<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>dont reinvent велосипед</title><link href="https://dontreinventbicycle.com/" rel="alternate"></link><link href="https://dontreinventbicycle.com/feeds/atom.xml" rel="self"></link><id>https://dontreinventbicycle.com/</id><updated>2021-10-23T00:00:00+08:00</updated><entry><title>You should probably use enum flags</title><link href="https://dontreinventbicycle.com/enum-flags.html" rel="alternate"></link><published>2021-10-23T00:00:00+08:00</published><updated>2021-10-23T00:00:00+08:00</updated><author><name>Dafne Kiyui</name></author><id>tag:dontreinventbicycle.com,2021-10-23:/enum-flags.html</id><summary type="html">&lt;p&gt;Enum flags are a clever optimisation technique that make use of bitwise logic operations paired with &lt;a href="https://en.wikipedia.org/wiki/Enumerated_type"&gt;enumerated types&lt;/a&gt; that sadly see a lack of use in more modern codebases and languages such as JavaScript, in spite of &lt;code&gt;enum&lt;/code&gt;s being otherwise fairly popular in TypeScript.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Guild&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Mage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Rogue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Warrior …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Enum flags are a clever optimisation technique that make use of bitwise logic operations paired with &lt;a href="https://en.wikipedia.org/wiki/Enumerated_type"&gt;enumerated types&lt;/a&gt; that sadly see a lack of use in more modern codebases and languages such as JavaScript, in spite of &lt;code&gt;enum&lt;/code&gt;s being otherwise fairly popular in TypeScript.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Guild&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Mage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Rogue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Warrior&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the case of “regular enums” such as the above, a value could either be &lt;code&gt;Guild.Mage&lt;/code&gt; or &lt;code&gt;Guild.Warrior&lt;/code&gt; but not both. While this may be desirable for some cases, consider the following enum &lt;code&gt;Element&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Fire&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Water&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Electric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Grass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Ice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Rock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Psychic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Treating this like a regular enum would mean an &lt;code&gt;element&lt;/code&gt; property would either have to be one value or another or be an array of &lt;code&gt;Element&lt;/code&gt;s:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Weapon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;elements&lt;/span&gt;: &lt;span class="kt"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Doing this comes at the complication of needing to use array-specific methods to filter items in an array. Consider the following code filtering a list of weapons to find all weapons that contain the elements &lt;em&gt;Fire&lt;/em&gt; and &lt;em&gt;Electric&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;weapons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fire&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Electric&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Needless to say, this code would be even more complicated if we were to filter all weapons that are &lt;strong&gt;only&lt;/strong&gt; &lt;em&gt;Fire&lt;/em&gt; and &lt;em&gt;Electric&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Enum flags make use of bitwise operations and binary flags to be able to assign more then one value in the form of a binary flags. Consider that numbers can be represented in binary, each binary exponent could be used as a flag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;0&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Fire&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Water&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Electric&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Grass&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Ice&lt;/span&gt;
&lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Rock&lt;/span&gt;
&lt;span class="n"&gt;Psychic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A weapon with the elements &lt;em&gt;Fire&lt;/em&gt; and &lt;em&gt;Electric&lt;/em&gt; could then be represented by binary &lt;code&gt;000101&lt;/code&gt;&lt;sub&gt;2&lt;/sub&gt; or the decimal number &lt;code&gt;5&lt;/code&gt;, as illustrated below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;0&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Fire&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Water&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Electric&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Grass&lt;/span&gt;
&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Ice&lt;/span&gt;
&lt;span class="err"&gt;↓&lt;/span&gt; &lt;span class="n"&gt;Rock&lt;/span&gt;
&lt;span class="n"&gt;Psychic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With each exponent representing a flag for a specific element, it's now possible to reconstruct the &lt;code&gt;Element&lt;/code&gt; enum with binary flag values.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Element&lt;/th&gt;
&lt;th&gt;Exponent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fire&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Water&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Electric&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grass&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rock&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Psychic&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;While simply assigning the exponents of &lt;code&gt;2&lt;/code&gt; to each member of the element works, it looks a little messy and doesn't as effective communicate that the values represent binary flags.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Fire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Water&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Electric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Grass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Ice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Rock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;Psychic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Left_shift"&gt;bitwise left shift operator (&lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;)&lt;/a&gt; on the other hand does 👇 This is also the more common practice anyway.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Fire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Water&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Electric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Grass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Ice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Rock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Psychic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Just as a quick primer on the left shift operator, it effectively shifts the bits of a value by the number of bits indicated by the right hand side, discarding any excess bits.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;0001&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0010&lt;/span&gt;
&lt;span class="mf"&gt;0101&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1010&lt;/span&gt;
&lt;span class="mf"&gt;0001&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#binary_bitwise_operators"&gt;Binary bitwise operators&lt;/a&gt; can then be used to perform operations with these values. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR"&gt;Bitwise OR (&lt;code&gt;|&lt;/code&gt;)&lt;/a&gt; can for example be used to assign the values:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Assigning the elements Fire and Electric to a weapon&lt;/span&gt;
&lt;span class="nx"&gt;weapon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fire&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Electric&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 | 2 = 5&lt;/span&gt;

&lt;span class="c1"&gt;// Adding the element Psychic to the same weapon&lt;/span&gt;
&lt;span class="nx"&gt;weapon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Psychic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 5 | 64 = 69&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND"&gt;Bitwise AND (&lt;code&gt;&amp;amp;&lt;/code&gt;)&lt;/a&gt; on the other hand can be used for comparing values. Our previous filter function can now be simplified so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;weapons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FireAndElectric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fire&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Electric&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;FireAndElectric&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;gt; 0 if element has Fire &amp;amp; Electric&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A tonne easier and more versatile!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Try doing this with your element arrays 😉&lt;/span&gt;
&lt;span class="nx"&gt;weapons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FireAndElectric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fire&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Electric&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;FireAndElectric&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="code"></category><category term="optimise"></category><category term="improve"></category></entry><entry><title>4 years at Piktochart</title><link href="https://dontreinventbicycle.com/piktochart.html" rel="alternate"></link><published>2021-10-10T00:00:00+08:00</published><updated>2021-10-10T00:00:00+08:00</updated><author><name>Dafne Kiyui</name></author><id>tag:dontreinventbicycle.com,2021-10-10:/piktochart.html</id><summary type="html">&lt;p&gt;On &lt;time datetime="2021-07-27"&gt;Tuesday, the 27th of July 2021&lt;/time&gt;, I served my final day at &lt;a href="https://piktochart.com/"&gt;Piktochart&lt;/a&gt;. I spent 4 years and some change at the company, joining as a JavaScript intern and leaving a DevOp &amp;amp; backend engineer for &lt;a href="https://piktostory.com/"&gt;Piktostory&lt;/a&gt;. Piktochart was my first “real” job and having spent a good part of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;On &lt;time datetime="2021-07-27"&gt;Tuesday, the 27th of July 2021&lt;/time&gt;, I served my final day at &lt;a href="https://piktochart.com/"&gt;Piktochart&lt;/a&gt;. I spent 4 years and some change at the company, joining as a JavaScript intern and leaving a DevOp &amp;amp; backend engineer for &lt;a href="https://piktostory.com/"&gt;Piktostory&lt;/a&gt;. Piktochart was my first “real” job and having spent a good part of my early twenties there, there's a lot to look back and reflect on.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A screenshot of the Piktochart web editor" src="https://dontreinventbicycle.com/images/piktochart/piktochart-editor.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“An infographic maker, presentation creator, and report builder in one online platform. No graphic design skills needed.”
- A quote from the Piktochart homepage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In &lt;time datetime="2017-05"&gt;May 2017&lt;/time&gt;, I joined the company as a JavaScript intern. I didn't pass the interview for a full-time position as a JavaScript Developer but my good friend &lt;em&gt;Churchill&lt;/em&gt;, who was working at the company at the time, managed to convince the head of frontend to give me a shot. I didn't know much frontend at the time, working part-time in a company researching DevOps and GitOps workflows and part-time at the university I was enrolled in to expand on the subject of my degree's final year project; Both very backend heavy projects. What little frontend JavaScript development I knew, I learned by hacking things together. I failed to complete the frontend assessment for Piktochart, but I did do it in &lt;a href="https://cycle.js.org/"&gt;Cycle.js&lt;/a&gt;, which at the end of the day was what caught the attention of my technical interviewers. They'd turn out to be great friends and mentors of mine at the company 😇&lt;/p&gt;
&lt;p&gt;Cycle.js taught me to think differently. &lt;a href="https://en.wikipedia.org/wiki/Reactive_programming"&gt;Reactive programming&lt;/a&gt; at the time to me felt very theoretical and not something I had given much thought or practice. Wikipedia gives the following great example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;$=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Churchill&lt;/em&gt; introduced me to Cycle.js earlier that year and I was hooked. At Piktochart, I was able to expand on these skills with their then transitional Cycle.js + RxJS mixed codebase. A lot of my work as an intern and in my early months as a JavaScript developer revolved around refactoring components, converting them from legacy plain-old ES5 and jQuery to ES6 and Cycle.js. A lot of attention had to be paid to ensure that components would continue to interop well with legacy components. That meant writing code that sort of resembled this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intent$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;myCycleComponent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;intent$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;set_color&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getActiveItems&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;Editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;myCycleComponent done&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Right around the time that things were starting to pick up and it seemed like the team was making good progress towards a fully functional (paradigm) application powered by Cycle.js, an executive decision was made that the team would adopt &lt;a href="https://vuejs.org/"&gt;Vue.js&lt;/a&gt; instead. Comparing the state of Vue.js to Cycle.js today, it seems to have been a decision that paid off. The framework has become a lot more mature and a lot of the growing pains resolved. Vue.js wasn't always like that, however. TypeScript support was grossly lacking, way up to Vue 3.0, and patterns for solving problems (state management, &lt;code&gt;computed&lt;/code&gt; vs. &lt;code&gt;watch&lt;/code&gt;, &lt;code&gt;DOM&lt;/code&gt;-hooks) had still to be established. For a moment I explored &lt;a href="https://github.com/kiyui/vue-cycle"&gt;vue-cycle&lt;/a&gt; as a side project, inspired by &lt;a href="https://github.com/vuejs/vue-rx"&gt;vue-rx&lt;/a&gt; as a means to get around &lt;em&gt;learning new things&lt;/em&gt;. I think what made me the most bitter about the whole experience was that the team, the frontend team, was not consulted before the switch.&lt;/p&gt;
&lt;p&gt;My last project as part of the frontend team (JavaScript developer) was to develop a &lt;a href="https://en.wikipedia.org/wiki/Collaborative_real-time_editor"&gt;collaborative real-time editor&lt;/a&gt; or “live-collaboration” as we called it. The goal was to build a Google Docs-like editor where multiple people could collaborate on the same project at the same time. Before I left the company, I made a screen-recording of the project thinking it would look great as a showcase on my CV. Below is a that, admittedly cheesy, recording:&lt;/p&gt;
&lt;video controls&gt;
  &lt;source src="https://dontreinventbicycle.com/images/piktochart/live-collaboration.mp4" type="video/mp4"&gt;
  Your browser does not support the video tag.
&lt;/video&gt;

&lt;p&gt;The architecture was inspired by Cycle.js's &lt;a href="https://cycle.js.org/model-view-intent.html"&gt;Model-View-Intent&lt;/a&gt;. It was a giant team effort to standardize a message format that could be used on something of an event bus, especially catering for stateful interactions (think drag and drop). It's still one of my proudest accomplishments, the complexity of it all. The project unfortunately never quite saw its full potential, remaining in beta since early 2019.&lt;/p&gt;
&lt;p&gt;After live-collaboration, I dedicated my time completely to devops tasks. As a member of frontend, I introduced Docker Compose working environments, TypeScript, &amp;amp; Cypress, on top of other meta-ventures surrounding devux like drastically improving build times 🎉 I still however had an urge to dig deeper. &lt;a href="https://archlinux.org/"&gt;Arch&lt;/a&gt;-user syndrome perhaps? Parallel to my work in frontend, I started picking up small devops tasks to familiarise myself with the technologies. The gradual transition allowed me to be an equally useful asset as a devop as I was a frontend developer without having to deal with the inertia of getting the ball rolling. I have to also admit that the switch was also fuelled by my distaste for Vue.js &amp;amp; general disagreements I had with our frontend lead about the patterns we used to problem-solve. &lt;/p&gt;
&lt;p&gt;I enjoyed shadowing my senior at the time, picking up skills such as Kubernetes and learning how the entire application was built and managed from the ground up. It sure as hell beats trying to learn these things from scratch or building up the skills from a couple of tutorials. Tutorials lack the depth, complexity, and issues of a live deployment. I even learned how to cause a downtime (on staging, unfortunately). Later that year, Q3 2019, I'd end up joining the &lt;a href="https://piktostory.com/"&gt;Piktostory&lt;/a&gt; team as their sole backend engineer and devop. Things do have a funny way of coming around 😊&lt;/p&gt;
&lt;p&gt;&lt;img alt="A screenshot of the Piktostory web editor" src="https://dontreinventbicycle.com/images/piktochart/piktostory-editor.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Easily turn one video into multiple content pieces.”
- A quote from the Piktostory homepage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I really enjoyed working on Piktostory. We started off a small team of 3 devs, 1 UX, and 1 PM, trying to figure this one product out. Developer responsibilities were very blurred, so we each got to play with every part of the stack. I wasn't just working a new feature sprint to sprint or managing the deployment of a black box. I was familiar with everything running throughout the stack, and that was awesome. At Piktostory, I had the freedom to explore my idea of a “dream” project because nothing was established yet; We had a pretty awesome (objectively, actually) GitOps workflow with continuous delivery. We used &lt;a href="https://svelte.dev/"&gt;Svelte&lt;/a&gt; on the frontend so we all got the explore the bleeding edge of frontend technologies. We also settled on using Firebase everything, so we all got to pick up things like &lt;a href="https://firebase.google.com/docs/firestore/"&gt;Firestore&lt;/a&gt; and &lt;a href="https://firebase.google.com/docs/functions/"&gt;Firebase Cloud Functions&lt;/a&gt;, both of which are genuinely pretty great to work with. Oh, and did I mention that I got to write code for a web-based video editor? 🤯&lt;/p&gt;
&lt;p&gt;I don't know where I was going with this &amp;amp; while it seems abundantly clear I see the passage of time in technologies I have used, that's not to say unwell of any of the personal connections I've made in Piktochart. Culture was a strong emphasis at the company and something I think they cultivated well; You will make friends here. That all said however I still tendered my resignation. Following mine, so did 3 other colleagues (&amp;amp; close friends) within the span of a month. Perhaps it's COVID, or perhaps it's the pent up frustration over squabbles &amp;amp; disagreements with leadership over time, but my heart was no longer in the company and the same adoration I had for the technologies I was working with gone. Was that what kept me there?&lt;/p&gt;
&lt;p&gt;Since my resignation, I've written this one blog-post and played around with &lt;a href="https://nixos.org/"&gt;Nix&lt;/a&gt;. Check out &lt;a href="https://deadinsi.de/@dafne/106800897694408275"&gt;this&lt;/a&gt; and &lt;a href="https://deadinsi.de/@dafne/106908614671002232"&gt;this&lt;/a&gt; post on my Mastodon for a mini-insight into the shenanigans I've done with that. Can you believe I switched to NixOS after 9 years on Arch Linux? I also rejected a couple companies at offer-level, got ghosted by a few, and had one offer I was really excited about completely crumble because &lt;strong&gt;bureaucracy&lt;/strong&gt; 💕 Playing Minecraft or re-watching Avatar (not the blue aliens) for the hundredth time isn't quite doing it for me either.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A screenshot of some messages I sent on Telegram. They read, “i have finally reached rock bottom”, “idk what to do with my life anymore”, ”i put octomore in a starbucks mocha frap nd it's fucking amazing”, “where do i go from here?”" src="https://dontreinventbicycle.com/images/piktochart/rock-bottom.png"&gt;&lt;/p&gt;
&lt;p&gt;At least some vices never change.&lt;/p&gt;
&lt;p&gt;So I think I'm going to give things a different go this time round; Try out a company working on something I'm passionate about. I still enjoy messing about with tech, but perhaps it's best my passion for it be defined in my own terms.&lt;/p&gt;</content><category term="personal"></category><category term="devux"></category><category term="devops"></category><category term="javascript"></category><category term="typescript"></category><category term="rxjs"></category><category term="svelte"></category><category term="vue"></category><category term="firebase"></category></entry><entry><title>installing google-cloud-sdk on apple silicon m1 (early 2021)</title><link href="https://dontreinventbicycle.com/m1-gcloud-early-2021.html" rel="alternate"></link><published>2021-01-08T00:00:00+08:00</published><updated>2021-01-08T00:00:00+08:00</updated><author><name>Dafne Kiyui</name></author><id>tag:dontreinventbicycle.com,2021-01-08:/m1-gcloud-early-2021.html</id><summary type="html">&lt;p&gt;If you were to attempt to install &lt;code&gt;google-cloud-sdk&lt;/code&gt; natively (via &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt;) on Apple Silicon, you'd get the following error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ brew install --cask google-cloud-sdk
&lt;span class="c1"&gt;# etc...&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; Installing Cask google-cloud-sdk
Beginning update. This process may take several minutes.
ERROR: &lt;span class="o"&gt;(&lt;/span&gt;gcloud.components.update&lt;span class="o"&gt;)&lt;/span&gt; The following components are unknown &lt;span class="o"&gt;[&lt;/span&gt;kuberun, anthoscli&lt;span class="o"&gt;]&lt;/span&gt;.
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; Purging files &lt;span class="k"&gt;for&lt;/span&gt; version …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;If you were to attempt to install &lt;code&gt;google-cloud-sdk&lt;/code&gt; natively (via &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt;) on Apple Silicon, you'd get the following error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ brew install --cask google-cloud-sdk
&lt;span class="c1"&gt;# etc...&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; Installing Cask google-cloud-sdk
Beginning update. This process may take several minutes.
ERROR: &lt;span class="o"&gt;(&lt;/span&gt;gcloud.components.update&lt;span class="o"&gt;)&lt;/span&gt; The following components are unknown &lt;span class="o"&gt;[&lt;/span&gt;kuberun, anthoscli&lt;span class="o"&gt;]&lt;/span&gt;.
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; Purging files &lt;span class="k"&gt;for&lt;/span&gt; version latest of Cask google-cloud-sdk
&lt;span class="c1"&gt;# etc...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The general consensus points to running &lt;code&gt;gcloud&lt;/code&gt; under Rosetta 2 for now (&lt;a href="https://stackoverflow.com/a/65119551"&gt;Example StackOverflow comment&lt;/a&gt;). However, thanks to &lt;a href="https://www.reddit.com/r/googlecloud/comments/kfbya3/gcloud_for_apple_silicon/ggapdli?context=3"&gt;this particular comment on Reddit&lt;/a&gt;, I've been able to get &lt;code&gt;google-cloud-sdk&lt;/code&gt; working just fine! The trick is to just modify the install script for &lt;code&gt;google-cloud-sdk&lt;/code&gt;. To do this, you'll want to run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ brew edit --cask google-cloud-sdk
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The default content should be something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;cask&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;google-cloud-sdk&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# etc...&lt;/span&gt;
  &lt;span class="n"&gt;postflight&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;system_command&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;staged_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/install.sh&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="ss"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
                     &lt;span class="s2"&gt;&amp;quot;--usage-reporting&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;false&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;--bash-completion&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;false&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;--path-update&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;false&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="s2"&gt;&amp;quot;--rc-path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;false&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;--quiet&amp;quot;&lt;/span&gt;
                   &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="ss"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;CLOUDSDK_PYTHON&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Formula&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;opt_bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python3&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# etc...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What you'll want to do here is append the arguments &lt;code&gt;--override-components core&lt;/code&gt; to the &lt;code&gt;system_command&lt;/code&gt; directive, like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;18c18
&amp;lt;                      &amp;quot;--rc-path&amp;quot;, &amp;quot;false&amp;quot;, &amp;quot;--quiet&amp;quot;
&lt;span class="gd"&gt;---&lt;/span&gt;
&amp;gt;                      &amp;quot;--rc-path&amp;quot;, &amp;quot;false&amp;quot;, &amp;quot;--quiet&amp;quot;, &amp;quot;--override-components&amp;quot;, &amp;quot;core&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After this, &lt;code&gt;gcloud&lt;/code&gt; should have installed perfectly! 🥳 Assuming of course that you have sourced the &lt;code&gt;path.zsh.inc&lt;/code&gt; &amp;amp; &lt;code&gt;completion.zsh.inc&lt;/code&gt; files as prompted by the &lt;code&gt;brew&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;You can investigate additional components that can be installed by running &lt;code&gt;gcloud components list&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;????????????????????????????????????????????????????????????????????????????????????????????????????????
?                                              Components                                              ?
????????????????????????????????????????????????????????????????????????????????????????????????????????
?     Status    ?                      Name                      ?            ID            ?   Size   ?
????????????????????????????????????????????????????????????????????????????????????????????????????????
? Not Installed ? BigQuery Command Line Tool                     ? bq                       ?  &amp;lt; 1 MiB ?
? Not Installed ? Cloud Datalab Command Line Tool                ? datalab                  ?  &amp;lt; 1 MiB ?
? Not Installed ? Cloud Datastore Emulator                       ? cloud-datastore-emulator ? 18.4 MiB ?
? Not Installed ? Cloud Firestore Emulator                       ? cloud-firestore-emulator ? 41.5 MiB ?
? Not Installed ? Cloud Pub/Sub Emulator                         ? pubsub-emulator          ? 56.4 MiB ?
? Not Installed ? gcloud Alpha Commands                          ? alpha                    ?  &amp;lt; 1 MiB ?
? Not Installed ? gcloud Beta Commands                           ? beta                     ?  &amp;lt; 1 MiB ?
? Not Installed ? gcloud app Java Extensions                     ? app-engine-java          ? 59.6 MiB ?
? Not Installed ? gcloud app PHP Extensions                      ? app-engine-php           ? 21.9 MiB ?
? Not Installed ? gcloud app Python Extensions                   ? app-engine-python        ?  6.1 MiB ?
? Not Installed ? gcloud app Python Extensions (Extra Libraries) ? app-engine-python-extras ? 27.1 MiB ?
? Not Installed ? kubectl                                        ? kubectl                  ?  &amp;lt; 1 MiB ?
? Not Installed ? pkg                                            ? pkg                      ?          ?
? Installed     ? Cloud SDK Core Libraries                       ? core                     ? 16.1 MiB ?
? Installed     ? Cloud Storage Command Line Tool                ? gsutil                   ?  3.5 MiB ?
????????????????????????????????????????????????????????????????????????????????????????????????????????
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In my case, I was able to install the &lt;code&gt;gsutil&lt;/code&gt; component by running &lt;code&gt;gcloud components install gsutil&lt;/code&gt;. Additional components will be subject to it working for Apple Silicon for now until a supported update becomes available.&lt;/p&gt;</content><category term="tips"></category><category term="gcloud"></category><category term="macos"></category></entry><entry><title>how to fix the zsh setup on macos</title><link href="https://dontreinventbicycle.com/macos-missing-zprofile.html" rel="alternate"></link><published>2021-01-07T00:00:00+08:00</published><updated>2021-01-07T00:00:00+08:00</updated><author><name>Dafne Kiyui</name></author><id>tag:dontreinventbicycle.com,2021-01-07:/macos-missing-zprofile.html</id><summary type="html">&lt;p&gt;Apple has defaulted macOS to using the &lt;code&gt;zsh&lt;/code&gt; shell since &lt;a href="https://support.apple.com/en-us/HT208050"&gt;macOS Catalina&lt;/a&gt;. For many, the switch (if they weren't already using &lt;code&gt;zsh&lt;/code&gt;) was as simple as running &lt;code&gt;chsh -s /bin/zsh&lt;/code&gt; and everything would appear to be working just fine.&lt;/p&gt;
&lt;p&gt;On brand new Macs however, both the &lt;code&gt;.zprofile&lt;/code&gt; and &lt;code&gt;.zshrc …&lt;/code&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Apple has defaulted macOS to using the &lt;code&gt;zsh&lt;/code&gt; shell since &lt;a href="https://support.apple.com/en-us/HT208050"&gt;macOS Catalina&lt;/a&gt;. For many, the switch (if they weren't already using &lt;code&gt;zsh&lt;/code&gt;) was as simple as running &lt;code&gt;chsh -s /bin/zsh&lt;/code&gt; and everything would appear to be working just fine.&lt;/p&gt;
&lt;p&gt;On brand new Macs however, both the &lt;code&gt;.zprofile&lt;/code&gt; and &lt;code&gt;.zshrc&lt;/code&gt; dotfiles of new users are not automatically populated in &lt;code&gt;$HOME&lt;/code&gt;. Even upon creating a &lt;code&gt;~/.zshrc&lt;/code&gt; file, it may seem as if the file is not sourced/loaded upon starting a new terminal session.&lt;/p&gt;
&lt;p&gt;The workaround for this is to create a &lt;code&gt;~/.zprofile&lt;/code&gt; file with the following contents:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[[&lt;/span&gt; -f ~/.zshrc &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will result in a much cleaner shell, loading &lt;code&gt;/etc/zprofile&lt;/code&gt;, &lt;code&gt;/etc/zshrc&lt;/code&gt;, and your local configuration without having to resolve to hacks with ugly &lt;code&gt;clear&lt;/code&gt; flashes such as adding a start-up command like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# I was misguided by terrible tutorials on the webbernet 😡, do not do this!&lt;/span&gt;
zsh&lt;span class="p"&gt;;&lt;/span&gt; clear
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="tips"></category><category term="shell"></category><category term="macos"></category></entry><entry><title>how to use a valid email when signing up for Sephora</title><link href="https://dontreinventbicycle.com/sephora-email-compliance.html" rel="alternate"></link><published>2020-11-24T00:00:00+08:00</published><updated>2020-11-24T00:00:00+08:00</updated><author><name>Dafne Kiyui</name></author><id>tag:dontreinventbicycle.com,2020-11-24:/sephora-email-compliance.html</id><summary type="html">&lt;p&gt;&lt;em&gt;Please don't take anything in this article seriously.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gmail.com/"&gt;Gmail&lt;/a&gt;, &lt;a href="https://outlook.com/"&gt;Outlook&lt;/a&gt;, and &lt;a href="https://mail.yahoo.com/"&gt;Yahoo! Mail&lt;/a&gt; are popular options for creating incredible email addresses that will be compliant with most software and are allowed to bypass sophisticated spam filters as the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gmail.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;yahoo.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;endsWith …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Please don't take anything in this article seriously.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gmail.com/"&gt;Gmail&lt;/a&gt;, &lt;a href="https://outlook.com/"&gt;Outlook&lt;/a&gt;, and &lt;a href="https://mail.yahoo.com/"&gt;Yahoo! Mail&lt;/a&gt; are popular options for creating incredible email addresses that will be compliant with most software and are allowed to bypass sophisticated spam filters as the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gmail.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;yahoo.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yahoo.co.uk&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;hotmail.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;live.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;hotmail.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;echo &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;$emailContent&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s2"&gt; | mail -s Fwd &lt;/span&gt;&lt;span class="si"&gt;$receiver&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// T0DO reports the error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I use &lt;a href="https://protonmail.com/"&gt;Protonmail&lt;/a&gt; with a custom domain, so I've become accustomed to having my email not be accepted by most websites. Even regular Protonmail addresses ending with &lt;code&gt;protonmail.ch&lt;/code&gt; instead of &lt;code&gt;protonmail.com&lt;/code&gt; are regularly blocked by websites because whitelist code the developer copied from &lt;a href="https://stackoverflow.com/"&gt;StackOverflow&lt;/a&gt; was not up to date. Therefore, I was not at all surprised to see the following error when trying to sign in to Sephora:&lt;/p&gt;
&lt;p&gt;&lt;img alt="The Sephora login form with the error messages “We're sorry, there is an error with your email and/or password. Remember passwords are 4 to 12 characters (letters or numbers) long. Please try again.” and “Please enter an e-mail address in the format username@domain.com.”" src="https://dontreinventbicycle.com/images/sephora-email-compliance/login-error.png"&gt;&lt;/p&gt;
&lt;p&gt;The key here is that I was &lt;em&gt;signing in&lt;/em&gt;, not signing up for an account. In fact, I have been signing into the mobile app just fine. Additionally, the “Continue” button did not trigger any requests, leading me to think that the restriction is indeed only on the frontend.&lt;/p&gt;
&lt;p&gt;With a little digging, I was able to source their email validation function. It appears that they are using a modified version of the RFC-5322 specification.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/**&lt;/span&gt;
&lt;span class="cm"&gt; * Note that our test for validity is much stricter than RFC-5322&amp;#39;s definition&lt;/span&gt;
&lt;span class="cm"&gt; * @param el&lt;/span&gt;
&lt;span class="cm"&gt; * @returns {boolean}&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isValidEmailAddress&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Well there's your problem! The regex restricts the email top-level-domain to 2 to 4 characters only, which is why my email at &lt;code&gt;dafne.rocks&lt;/code&gt; did not work! This simple patch should surely fix it now:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; function isValidEmailAddress (login) {
&lt;span class="gd"&gt;-  var pattern = /^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/i;&lt;/span&gt;
&lt;span class="gi"&gt;+  var pattern = /^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,5})$/i;&lt;/span&gt;
   return pattern.test(login);
 },
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This new specification is available as Dafne Kiyui's modification to Sephora's RFC-5322 specification modification.&lt;/p&gt;</content><category term="joke"></category><category term="javascript"></category><category term="php"></category><category term="regex"></category></entry><entry><title>Free and open source software and GitHub</title><link href="https://dontreinventbicycle.com/foss-and-github.html" rel="alternate"></link><published>2020-11-05T00:00:00+08:00</published><updated>2020-11-05T00:00:00+08:00</updated><author><name>Dafne Kiyui</name></author><id>tag:dontreinventbicycle.com,2020-11-05:/foss-and-github.html</id><summary type="html">&lt;p&gt;The news that &lt;a href="https://youtube-dl.org/"&gt;youtube-dl&lt;/a&gt; had been struck by DMCA takedown notice by RIAA has been circulating in many tech circles such as &lt;a href="https://news.ycombinator.com/item?id=24872911"&gt;Hacker News&lt;/a&gt;, inviting regular criticism of GitHub and the volatility of software hosting platforms. Akin to &lt;a href="https://news.ycombinator.com/item?id=17221527"&gt;when Microsoft acquired GitHub&lt;/a&gt; or when &lt;a href="https://news.ycombinator.com/item?id=20542731"&gt;GitHub banned users in Iran&lt;/a&gt; (among …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The news that &lt;a href="https://youtube-dl.org/"&gt;youtube-dl&lt;/a&gt; had been struck by DMCA takedown notice by RIAA has been circulating in many tech circles such as &lt;a href="https://news.ycombinator.com/item?id=24872911"&gt;Hacker News&lt;/a&gt;, inviting regular criticism of GitHub and the volatility of software hosting platforms. Akin to &lt;a href="https://news.ycombinator.com/item?id=17221527"&gt;when Microsoft acquired GitHub&lt;/a&gt; or when &lt;a href="https://news.ycombinator.com/item?id=20542731"&gt;GitHub banned users in Iran&lt;/a&gt; (among other countries), there was an influx of people criticising GitHub and &lt;a href="https://twitter.com/gitlabstatus/status/1003887898142367744"&gt;reactionarily hopping onto GitLab&lt;/a&gt;, which ironically is affected by the same sanctions, instead.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Repository unavailable due to DMCA takedown.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This repository is currently disabled due to a DMCA takedown notice. We have disabled public access to the repository. The notice has been &lt;a href="https://github.com/github/dmca/blob/master/2020/10/2020-10-23-RIAA.md"&gt;publicly posted&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you are the repository owner, and you believe that your repository was disabled as a result of mistake or misidentification, you have the right to file a counter notice and have the repository reinstated. Our help articles provide more details on our &lt;a href="https://docs.github.com/articles/dmca-takedown-policy"&gt;DMCA takedown&lt;/a&gt; policy and &lt;a href="https://docs.github.com/articles/guide-to-submitting-a-dmca-counter-notice"&gt;how to file a counter notice&lt;/a&gt;. If you have any questions about the process or the risks in filing a counter notice, we suggest that you consult with a lawyer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I'd like to discuss and propose a different reason to "jump to GitLab" however. I received the book &lt;cite&gt;Working in Public: The Making and Maintenance of Open Source Software&lt;/cite&gt; by Nadia Eghbal in the mail a few weeks ago from an anonymous sender 😱, where it discusses how GitHub had become the de-facto platform for hosting open source code:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Although there's no requirement that developers must use GitHub to write open source software, GitHub is by far the dominant code-hosting platform today &lt;em&gt;(page 21)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;An irony, considering that GitHub is a proprietary service. Also cited in the book is that large open source projects such as Python and the &lt;a href="https://github.blog/2019-04-29-apache-joins-github-community/"&gt;Apache Software Foundation have moved to GitHub&lt;/a&gt;. In discussing &lt;a href="https://snarky.ca/the-history-behind-the-decision-to-move-python-to-github/"&gt;the history behind the decision to move Python to GitHub&lt;/a&gt;, Brett Cannon said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...there was no killer feature that GitLab had. Now some would argue that the fact GitLab is open source is its killer feature. But to me, the development process is more important than worrying whether a cloud-based service publishes its source code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To echo the sentiment of user &lt;em&gt;gyka&lt;/em&gt; on the &lt;a href="https://news.ycombinator.com/item?id=13615139"&gt;Hacker News discussion thread&lt;/a&gt;, &lt;em&gt;I really dislike the trend of open-source software centralising around GitHub&lt;/em&gt;, I too dislike GitHub becoming a central authority in the development of open source software, but I do not think that jumping to GitLab is the solution. GitHub owes its success to introducing the notion of "social coding" and main-streaming the development process for developers worldwide. This has made it easier for developers to get their projects discovered and have users report bugs with relatively little friction. GitLab, while maintaining an open source community edition, does not do much to set itself apart to the larger free and open source (FOSS) community. &lt;em&gt;Side note: It is however a great tool for the DevOps &amp;amp; GitOps workflow.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When thinking of both GitHub and GitLab as platforms, rather than self-hosted GitLab - the software, the important distinction to the FOSS community is its commitment to the maintenance of such software. There is a difference between the FOSS community and the larger "GitHub generation of developers", as &lt;em&gt;Working in Public&lt;/em&gt; highlights,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Both free and early open source advocates were preoccupied with evangelizing the idea of open source...but today's developers hardly even notice "open source" as a concept anymore. They just want to write and publish their code, and GitHub is the platform that makes it easy to do that. &lt;em&gt;(page 28)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To early free and open source developers, the move towards standard tools and workflows, shepherded by a single company like GitHub (acquired by Microsoft in 2018), represented a backslide against everything they had been fighting for since the 1980s. Code collaboration wasn't supposed to belong to anyone, and especially not to a multibillion-dollar company making proprietary software. &lt;em&gt;(page 30)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But the GitHub generation of open source developers doesn't see it that way, because they prioritise convenience over freedom (unlike free software advocates) or openness (unlike early open source advocates). &lt;em&gt;(page 30)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is where &lt;a href="https://codeberg.org/"&gt;Codeberg&lt;/a&gt; differs. Codeberg is a non-profit launched to give open source software a home, with &lt;a href="https://blog.codeberg.org/codebergorg-launched.html"&gt;a clear mission statement&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The mission of the Codeberg e.V. is to build and maintain a free collaboration platform for creating, archiving, and preserving code and to document its development process.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This distinction matters to me and is why I opted to also provide a source download for my &lt;a href="/rewriting-night-light.html"&gt;night light slider rewrite&lt;/a&gt; instead of only just linking to the GitHub pull request. Accessibility matters, and for individuals that may want to avoid using GitHub or non-free software altogether, &lt;a href="https://codeberg.org/"&gt;Codeberg&lt;/a&gt; is a great alternative, which is why I've decided to move and start developing my FOSS software there instead, starting with my GNOME extensions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://codeberg.org/kiyui/gnome-shell-night-light-slider-extension"&gt;gnome-shell-night-light-slider-extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codeberg.org/kiyui/gnome-shell-screenshotlocations-extension"&gt;gnome-shell-screenshotlocations-extension&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The migration was relatively simple, especially with the &lt;a href="https://docs.gitea.io/en-us/migrations-interfaces/"&gt;migration feature&lt;/a&gt; which even imports old pull requests and issues. I have a tutorial on doing just that &lt;a href="/github-to-codeberg-howto.html"&gt;here&lt;/a&gt;. Regardless, I invite more thought on this subject and can only wonder where the FOSS community goes with this in the future.&lt;/p&gt;
&lt;h3&gt;closing notes&lt;/h3&gt;
&lt;p&gt;I do not see myself moving away from GitHub permanently as it continues to play a vital role in how and where software is developed (and my employer uses it 🤷). This site is, and continues to be, hosted on &lt;a href="https://pages.github.com/"&gt;GitHub pages&lt;/a&gt; because convenience triumphs and it's &lt;em&gt;free&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I do hope that recruiters &lt;a href="https://news.ycombinator.com/item?id=19413348"&gt;stop requiring that a GitHub profile be linked&lt;/a&gt; however because it's an awfully superficial way to judge the quality of a candidate.&lt;/p&gt;
&lt;h3&gt;citation&lt;/h3&gt;
&lt;p&gt;I quoted the book &lt;cite&gt;Working in Public: The Making and Maintenance of Open Source Software&lt;/cite&gt; by Nadia Eghbal (Stripe Press), quite a bit and can very much vouch to it being a great read. It's available for purchase &lt;a href="https://www.amazon.com/dp/0578675862/"&gt;on Amazon&lt;/a&gt; (and hopefully elsewhere).&lt;/p&gt;</content><category term="code"></category><category term="hosting"></category><category term="git"></category></entry><entry><title>how to migrate your GitHub repository to Codeberg</title><link href="https://dontreinventbicycle.com/github-to-codeberg-howto.html" rel="alternate"></link><published>2020-11-04T00:00:00+08:00</published><updated>2020-11-04T00:00:00+08:00</updated><author><name>Dafne Kiyui</name></author><id>tag:dontreinventbicycle.com,2020-11-04:/github-to-codeberg-howto.html</id><summary type="html">&lt;p&gt;The easiest way to migrate repositories is to simply change where your local repository remote points to. In a locally cloned repository, assert that you should have a git remote (usually named &lt;code&gt;origin&lt;/code&gt;) by running &lt;code&gt;git remote -v&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="nv"&gt;@github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;com&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="nv"&gt;@github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;com …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;The easiest way to migrate repositories is to simply change where your local repository remote points to. In a locally cloned repository, assert that you should have a git remote (usually named &lt;code&gt;origin&lt;/code&gt;) by running &lt;code&gt;git remote -v&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="nv"&gt;@github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;com&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="nv"&gt;@github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;com&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On your &lt;a href="https://codeberg.org/"&gt;Codeberg&lt;/a&gt; dashboard, create a new repository (&lt;a href="https://docs.codeberg.org/getting-started/first-repository/"&gt;guide&lt;/a&gt;) and copy its upstream URL. You can then simply update the remote by running,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git remote set-url origin git@codeberg.org:username/repository.git
git push -u origin master &lt;span class="c1"&gt;# Or whatever other branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;maintaining a mirror&lt;/h3&gt;
&lt;p&gt;If you're not interested in fully migrating your repository over to Codeberg and would rather maintain a mirror, you can simply set up an additional remote as so,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# You can name the remote something other than codeberg&lt;/span&gt;
git remote add codeberg git@codeberg.org:username/repository.git
git push codeberg master &lt;span class="c1"&gt;# Or whatever other branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This of course means that in addition to just running &lt;code class="highlight"&gt;git push&lt;/code&gt; to push to a branch's regular upstream, you'd have to run &lt;code class="highlight"&gt;git push codeberg&lt;/code&gt; as well.&lt;/p&gt;
&lt;h3&gt;using the Gitea migration feature&lt;/h3&gt;
&lt;p&gt;Codeberg also offers a &lt;a href="https://docs.gitea.io/en-us/migrations-interfaces/"&gt;migration interface&lt;/a&gt; that can import Issues, Pull Requests, Releases, and more (&lt;a href="https://codeberg.org/kiyui/gnome-shell-night-light-slider-extension/issues/76"&gt;an example imported issue&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;To do this, you'll want to create a new &lt;a href="https://github.com/settings/tokens"&gt;personal access token&lt;/a&gt; by visiting &lt;a href="https://github.com/settings/tokens/new"&gt;this link&lt;/a&gt;. Under the &lt;strong&gt;“select scopes”&lt;/strong&gt; header, you'll want to make sure you check the &lt;strong&gt;“repo”&lt;/strong&gt; scope, giving it access to your repositories. You can read more on how to do this under the &lt;a href="https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token"&gt;official GitHub documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next, you'll want to visit the Codeberg &lt;a href="https://codeberg.org/repo/migrate"&gt;“New Migration”&lt;/a&gt; page. This can also be done by clicking the “New Migration” option under the same menu on the top right where you'd regularly create a new repository.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The Codeberg new repository dropdown, with “New migration” listed as one of the options" src="https://dontreinventbicycle.com/images/github-to-codeberg-howto/codeberg-new-repo.png"&gt;&lt;/p&gt;
&lt;p&gt;On the &lt;a href="https://codeberg.org/repo/migrate"&gt;“New Migration”&lt;/a&gt; page, you should be greeted with a form split into 2 sections. Under the first section,&lt;/p&gt;
&lt;p&gt;You'll want to populate the “Migrate/Clone From URL” input with your &lt;strong&gt;GitHub HTTPS clone URL&lt;/strong&gt;. This is important such that other items would be made available under the “Migration Items” section.&lt;/p&gt;
&lt;p&gt;Under “Clone Authorization”, you'll want to have your username filled as usual but have the &lt;strong&gt;GitHub access token set as the password&lt;/strong&gt;. ⚠️  Do not use your actual password here!&lt;/p&gt;
&lt;p&gt;The rest, such as the “Repository Name”, “Visibility”, “Migration Items”, and “Description” can be filled to your discretion. &lt;/p&gt;
&lt;p&gt;&lt;img alt="The Codeberg migration interface, with “https://github.com/username/repository.git” as the “Migrate/Clone From URL”, the username &amp;amp; password filled under “Clone Authorization”, and in a separate section all the import details such as the “Repository Name”, “Visibility”, “Migration Items” (consisting of checkboxes for “Wiki“, “Milestones“, “Labels“, “Issues“, “Pull Requests“, and “Releases“), and the “Description”" src="https://dontreinventbicycle.com/images/github-to-codeberg-howto/codeberg-migration.png"&gt;&lt;/p&gt;
&lt;p&gt;Clicking the “Migrate Repository” should redirect you to a loading screen, and finally the brand new repository. Et voilà!&lt;/p&gt;</content><category term="tips"></category><category term="hosting"></category><category term="git"></category></entry><entry><title>rewriting "gnome screenshot locations"</title><link href="https://dontreinventbicycle.com/rewriting-screenshot-locations.html" rel="alternate"></link><published>2020-10-11T00:00:00+08:00</published><updated>2020-11-04T00:00:00+08:00</updated><author><name>Dafne Kiyui</name></author><id>tag:dontreinventbicycle.com,2020-10-11:/rewriting-screenshot-locations.html</id><summary type="html">&lt;p&gt;I know I previously said that I'd follow up my &lt;a href="/rewriting-night-light.html"&gt;rewriting "night light slider"&lt;/a&gt; article with that detailing the write up of the preferences panel,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This article would be followed up by a write-up of the preferences panel rewrite&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;...but I am a true open source developer that lacks accountability …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I know I previously said that I'd follow up my &lt;a href="/rewriting-night-light.html"&gt;rewriting "night light slider"&lt;/a&gt; article with that detailing the write up of the preferences panel,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This article would be followed up by a write-up of the preferences panel rewrite&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;...but I am a true open source developer that lacks accountability! And lack accountability, I do. My last "Screenshot Locations" &lt;a href="https://extensions.gnome.org/review/7669"&gt;update&lt;/a&gt; was on the 14th of November 2017, and even that was but a metadata bump to support &lt;a href="https://www.gnome.org/news/2017/09/gnome-3-26-released/"&gt;GNOME 3.26&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Even the repository had moved since!&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# remote: This repository moved. Please use the new location:&lt;/span&gt;
&lt;span class="c1"&gt;# remote:   git@github.com:kiyui/gnome-shell-screenshotlocations-extension.git&lt;/span&gt;
&lt;span class="c1"&gt;# remote:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://extensions.gnome.org/extension/1179/screenshot-locations/"&gt;Screenshot Locations&lt;/a&gt; extension works (or rather worked) by remapping GNOME's default screenshot keys to calls to &lt;a href="https://gitlab.gnome.org/GNOME/gnome-screenshot/"&gt;GNOME screenshot&lt;/a&gt;, which should respect the value set at &lt;code&gt;/org/gnome/gnome-screenshot/auto-save-directory&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;screenshotKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;area-screenshot&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;shortcut&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;Shift&amp;gt;Print&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;gnome-screenshot -a&amp;#39;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// etc&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;window-screenshot-clip&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;shortcut&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;Ctrl&amp;gt;&amp;lt;Alt&amp;gt;Print&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;gnome-screenshot -w -c&amp;#39;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The extension does so because curiously, GNOME Shell does not actually use GNOME Screenshot to create screenshots. Instead, referring to the &lt;a href="https://gitlab.gnome.org/GNOME/gnome-shell"&gt;gnome-shell repository&lt;/a&gt;, we find a &lt;code&gt;ScreenshotService&lt;/code&gt; defined at &lt;code&gt;js/ui/screenshot.js&lt;/code&gt;. In both GNOME 3.36 and 3.38, a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator"&gt;generator function&lt;/a&gt; called &lt;code&gt;__resolveRelativeFilename&lt;/code&gt; is used to generate file names for the stream of screenshots that will be created.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;_resolveRelativeFilename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\.png$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_user_special_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UserDirectory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DIRECTORY_PICTURES&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_home_dir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file_test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FileTest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXISTS&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;new_for_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_filenamev&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.png`&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;new_for_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build_filenamev&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.png`&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This can be confirmed by running the following code in &lt;a href="https://wiki.gnome.org/Projects/GnomeShell/LookingGlass"&gt;Looking Glass&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shellDBusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_screenshotService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_resolveRelativeFilename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;screenshot.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_path&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Notice how this appends a number after the first invocation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or visually,&lt;/p&gt;
&lt;p&gt;&lt;img alt="Trying out the resolveRelativeFilename generator in Looking Glass" src="https://dontreinventbicycle.com/images/rewriting-screenshot-locations/visually.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;the rewrite&lt;/h2&gt;
&lt;p&gt;The approach for the extension this time is to override the &lt;code&gt;_resolveRelativeFilename&lt;/code&gt; generator, injecting a user-provided directory instead of defaulting to the hard-coded values set by GNOME. I opted for writing my own schema instead of reusing the value from GNOME Screenshot at &lt;code&gt;/org/gnome/gnome-screenshot/auto-save-directory&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;schemalist&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;schema&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;org.gnome.shell.extensions.screenshotlocations&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/org/gnome/shell/extensions/screenshotlocations/&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;save-directory&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;default&amp;gt;&lt;/span&gt;&amp;#39;&amp;#39;&lt;span class="nt"&gt;&amp;lt;/default&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Screenshot directory&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;Manage where screenshots are saved&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/schema&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/schemalist&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Patching the generator is as simple as adding the &lt;code&gt;save-directory&lt;/code&gt; value to the list of seek-paths in the generator,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     let path = [
&lt;span class="gi"&gt;+        this._preferences.get_string(&amp;#39;save-directory&amp;#39;),&lt;/span&gt;
         GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES),
         GLib.get_home_dir(),
     ].find(p =&amp;gt; GLib.file_test(p, GLib.FileTest.EXISTS));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;..where the main function of the extension is to just perform this override:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Extension&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ExtensionUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shellDBusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_screenshotService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_original_resolveRelativeFilename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shellDBusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_screenshotService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_resolveRelativeFilename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shellDBusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_screenshotService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_resolveRelativeFilename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_resolveRelativeFilenameOverride&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;disable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shellDBusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_screenshotService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_resolveRelativeFilename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shellDBusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_screenshotService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_original_resolveRelativeFilename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shellDBusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_screenshotService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_original_resolveRelativeFilename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;_resolveRelativeFilenameOverride&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// etc&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Except that did not work.&lt;/p&gt;
&lt;p&gt;&lt;img alt="GNOME Extensions reporting that imports.ui.main.shellDBusService is null" src="https://dontreinventbicycle.com/images/rewriting-screenshot-locations/that-didnt-work.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Digging into the &lt;a href="https://gitlab.gnome.org/GNOME/gnome-shell"&gt;gnome-shell repository&lt;/a&gt;, we find that the &lt;code&gt;_initializeUI&lt;/code&gt; function, which in turn initializes all extensions, is called before the &lt;code&gt;shellDBusService&lt;/code&gt; is even initialized.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;_initializeUI&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// etc&lt;/span&gt;
&lt;span class="nx"&gt;shellDBusService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ShellDBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GnomeShell&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The correct way to detect when &lt;code&gt;shellDBusService&lt;/code&gt; is initialized would to over-engineer a solution, so instead I used a hack that ever JavaScript developer loves, &lt;a href="/gjs-set-timeout-interval.html"&gt;setTimeout&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PRIORITY_DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shellDBusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_screenshotService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_original_resolveRelativeFilename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shellDBusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_screenshotService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_resolveRelativeFilename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shellDBusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_screenshotService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_resolveRelativeFilename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_resolveRelativeFilenameOverride&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The amazing thing here is that because everything is run on a single thread, even a timeout of &lt;code&gt;0&lt;/code&gt; would suffice to make sure that &lt;code&gt;shellDBusService&lt;/code&gt; is initialized.&lt;/p&gt;
&lt;h2&gt;done&lt;/h2&gt;
&lt;p&gt;The result is a much simpler extension that should hopefully be easier to maintain. The entire rewrite is available on &lt;del&gt;GitHub&lt;/del&gt; &lt;a href="https://codeberg.org/kiyui/gnome-shell-screenshotlocations-extension/pulls/11"&gt;Codeberg&lt;/a&gt;, including an updated preferences panel that makes use of &lt;a href="https://developer.puri.sm/projects/libhandy/unstable/"&gt;libhandy&lt;/a&gt;.&lt;/p&gt;</content><category term="code"></category><category term="gnome-shell"></category><category term="gnome-shell-extension"></category><category term="gjs"></category><category term="gtk"></category></entry><entry><title>rewriting "night light slider"</title><link href="https://dontreinventbicycle.com/rewriting-night-light.html" rel="alternate"></link><published>2020-10-06T00:00:00+08:00</published><updated>2020-11-04T00:00:00+08:00</updated><author><name>Dafne Kiyui</name></author><id>tag:dontreinventbicycle.com,2020-10-06:/rewriting-night-light.html</id><summary type="html">&lt;p&gt;I'm the author of the &lt;a href="https://extensions.gnome.org/extension/1276/night-light-slider/"&gt;Night Light Slider extension&lt;/a&gt; for GNOME Shell. When I originally authored the extension in late 2017, my intention was to simply add a slider, kin to the brightness slider, to manage the temperature of the GNOME Shell "Night Light" feature. A couple of feature requests …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm the author of the &lt;a href="https://extensions.gnome.org/extension/1276/night-light-slider/"&gt;Night Light Slider extension&lt;/a&gt; for GNOME Shell. When I originally authored the extension in late 2017, my intention was to simply add a slider, kin to the brightness slider, to manage the temperature of the GNOME Shell "Night Light" feature. A couple of feature requests later however, we have the following preferences menu:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Night Light Slider preferences" src="https://dontreinventbicycle.com/images/rewriting-night-light/preferences.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Not only are the options rather obscured, they don't make too much sense either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;"Show always" would have the slider shown even when the night light is disabled;&lt;/li&gt;
&lt;li&gt;"Show status icon" would either enable or disable the night light indicator in the GNOME status area;&lt;/li&gt;
&lt;li&gt;"Enable always" would have the night light enabled at all times, unlike the default option where it'd run between a specified time period;&lt;/li&gt;
&lt;li&gt;"Brightness sync" would have the night light value also sync up with the brightness value (though not vice-versa);&lt;/li&gt;
&lt;li&gt;"Show in submenu" would have the slider appear in the night light submenu instead of besides the brightness slider;&lt;/li&gt;
&lt;li&gt;Two input fields "Minimum value" and "Maximum value" would allow you to override the default minimum and maximum temperatures for the night light;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not only am I a master of bad UX, but I've committed some &lt;b id="pretty-terrible-code"&gt;pretty terrible code&lt;/b&gt;. My &lt;a href="https://codeberg.org/kiyui/gnome-shell-night-light-slider-extension/commit/ed57dcb4ee3f42ede85d1addac1e3986925fd834#diff-493871bb0b725f5b874574677c336d1aR217"&gt;latest commit&lt;/a&gt; included the following bit of code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_hackyShowCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_icon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;show&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_icon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// etc.&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_hackyShowCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_icon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_hackyShowCallback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_icon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The code smelled bad to me but I genuinely did not know whether or not I was doing the right thing, and that's something that has bugged me ever since I started writing this extension. I haven't a clue what I'm doing or how to write GJS properly! The extension had been since the very beginning a hack for something I needed, and that I decided to share with the world.&lt;/p&gt;
&lt;p&gt;As a side note, I did get &lt;a href="https://extensions.gnome.org/review/17718"&gt;the following review&lt;/a&gt; for the above code:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;That is the correct way to disconnect signals. Typically (in C) signals are automatically disconnected when the reference count drops to 0. In GJS, any variable pointing to an Object will prevent that.
In the case of your extension, so long as your callbacks are only triggered from inside the Extension object and only access variables that are guaranteed to exist, you don't need to manually disconnect those signals.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;the rewrite&lt;/h2&gt;
&lt;p&gt;I decided I'd write my extension from scratch with the feature-set I wanted but thinking the design over properly from the start. The &lt;a href="https://web.archive.org/web/20200707175025/https://wiki.gnome.org/Projects/GnomeShell/Extensions/Writing"&gt;official guide&lt;/a&gt;, while dated, is a good starting place. For example, while it links to &lt;a href="https://gjs-docs.gnome.org/"&gt;gjs-docs.gnome.org&lt;/a&gt; instead of the defunct &lt;a href="#why-would-you-even-click-that-link"&gt;devdocs.baznga.org&lt;/a&gt;, you still find &lt;a href="https://web.archive.org/web/20200707175025/https://wiki.gnome.org/Projects/GnomeShell/Extensions/Writing#CA-24d96e269751d4aa12a4f5e51ef36748e95dacfb"&gt;references&lt;/a&gt; to it in the code.&lt;/p&gt;
&lt;style&gt;
#why-would-you-even-click-that-link {
  display: none;
}

#why-would-you-even-click-that-link:target {
  display: block;
}
&lt;/style&gt;

&lt;div id="why-would-you-even-click-that-link"&gt;
  &lt;p&gt;You clicked the defunct link didn't you? (Or you're using a text-only browser idk)&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;I'd optimally like to support all the features that I already have, have better localisation support, and a much cleaner preferences menu. I defined the following functional requirements for myself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sliding should change the night light temperature;&lt;/li&gt;
&lt;li&gt;Scrolling on the indicator should change the night light temperature;&lt;/li&gt;
&lt;li&gt;Ability to swap the axis of the slider;&lt;/li&gt;
&lt;li&gt;Ability to have the extension enable the night light indefinitely;&lt;/li&gt;
&lt;li&gt;Ability to define where in the panel menu the slider would show;&lt;/li&gt;
&lt;li&gt;Ability to toggle the visibility of the night light indicator in the status area;&lt;/li&gt;
&lt;li&gt;Ability to have the night light elements be shown or hidden when the night light is inactive;&lt;/li&gt;
&lt;li&gt;Ability to have the night light sync up with the system brightness;&lt;/li&gt;
&lt;li&gt;Ability to define the minimum and maximum temperature of the slider;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I started by using the &lt;code&gt;gnome-shell-extension-tool -c&lt;/code&gt; tool to scaffold the project. Even here, the scaffolded &lt;code&gt;extension.js&lt;/code&gt; already differs from the guide where it describes extensions as having the function hooks &lt;code&gt;init()&lt;/code&gt;, &lt;code&gt;enable()&lt;/code&gt;, and &lt;code&gt;disable()&lt;/code&gt;. Here instead we have a cleaner &lt;code&gt;init&lt;/code&gt; function that returns a new instance of the &lt;code&gt;Extension&lt;/code&gt; class that instead implements the &lt;code&gt;enable&lt;/code&gt; and &lt;code&gt;disable&lt;/code&gt; methods. This seems a lot cleaner!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Extension&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;disable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Extension&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We also repopulate our &lt;b id="metadata-file"&gt;extension metadata file&lt;/b&gt;, &lt;code&gt;metadata.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Night Light Slider&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Change night light temperature&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;settings-schema&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;org.gnome.shell.extensions.nightlightslider&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;uuid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;night-light-slider.timur@linux.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://github.com/kiyui/gnome-shell-night-light-slider-extension&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;shell-version&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;3.36&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, I figured I'd start from basics and imitate how the &lt;b id="brightness-slider"&gt;brightness slider&lt;/b&gt; is set up in the GNOME Shell. To do this, I cloned myself a local copy of the &lt;a href="https://gitlab.gnome.org/GNOME/gnome-shell"&gt;gnome-shell repository&lt;/a&gt; and referred to &lt;code&gt;js/ui/status/brightness.js&lt;/code&gt;. Everything under the &lt;code&gt;js/&lt;/code&gt; folder is available to us under &lt;code&gt;imports&lt;/code&gt; on top of what GJS already &lt;a href="https://gitlab.gnome.org/GNOME/gjs/-/blob/master/doc/Modules.md"&gt;provides us with&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;GJS imports&lt;/h3&gt;
&lt;p&gt;Modules in GJS work &lt;em&gt;slightly&lt;/em&gt; differently than they do in Node. There isn't a &lt;code&gt;require&lt;/code&gt; function but instead an &lt;code&gt;imports&lt;/code&gt; object (&lt;code&gt;GJSFileImporter&lt;/code&gt;) that exposes members in a module. Variables declared with &lt;code&gt;const&lt;/code&gt; or &lt;code&gt;let&lt;/code&gt; aren't exported, nor are function expressions or classes declared with the &lt;code&gt;class&lt;/code&gt; keyword, but regular functions declared with the &lt;code&gt;function&lt;/code&gt; keyword or variables initialized with &lt;code&gt;var&lt;/code&gt; are.&lt;/p&gt;
&lt;p&gt;This means that the &lt;code&gt;Slider&lt;/code&gt; class declared at &lt;code&gt;js/ui/slider.js&lt;/code&gt; should be available to me under &lt;code&gt;imports.ui.slider.Slider&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;Slider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerClass&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="cm"&gt;/* etc */&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Slider&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;BarLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BarLevel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* etc */&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;d-bus proxies &amp;amp; gsettings&lt;/h3&gt;
&lt;p&gt;The brightness slider works very simply by using a &lt;code&gt;Gio.DBusProxy&lt;/code&gt; proxy to both update and receive updates from changes to the system brightness by calling a private &lt;code&gt;_sync()&lt;/code&gt; method. It also uses the &lt;code&gt;imports.ui.slider.Slider&lt;/code&gt; class to render the brightness slider in the panel.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BrightnessProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BUS_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OBJECT_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;g-properties-changed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_slider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Slider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_sliderChangedId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;notify::value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_sliderChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Updates to the slider in turn call the &lt;code&gt;_sliderChanged&lt;/code&gt; method, which then updates the previously defined &lt;code&gt;_proxy&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;_sliderChanged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Brightness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Referring to &lt;code&gt;js/ui/status/nightLight.js&lt;/code&gt;, we see that a &lt;b id="similar-proxy"&gt;similar proxy&lt;/b&gt; to the night light properties exists.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ColorInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;loadInterfaceXML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;org.gnome.SettingsDaemon.Color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ColorProxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DBusProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;makeProxyWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ColorInterface&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we refer to &lt;code&gt;js/misc/fileUtils.js&lt;/code&gt; to understand what &lt;code&gt;loadInterfaceXML&lt;/code&gt; here will load. We optimally need a &lt;a href="https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format"&gt;D-Bus introspection specification&lt;/a&gt; for the &lt;code&gt;Temperature&lt;/code&gt; property. I actually have no clue where I got the &lt;code&gt;Temperature&lt;/code&gt; property from, but I did have the following lines of code in my extension previously:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ColorInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;node&amp;gt; \&lt;/span&gt;
&lt;span class="s1"&gt;&amp;lt;interface name=&amp;quot;org.gnome.SettingsDaemon.Color&amp;quot;&amp;gt; \&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;lt;property name=&amp;quot;Temperature&amp;quot; type=&amp;quot;d&amp;quot; access=&amp;quot;readwrite&amp;quot;/&amp;gt; \&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;lt;property name=&amp;quot;NightLightActive&amp;quot; type=&amp;quot;b&amp;quot; access=&amp;quot;read&amp;quot;/&amp;gt; \&lt;/span&gt;
&lt;span class="s1"&gt;&amp;lt;/interface&amp;gt; \&lt;/span&gt;
&lt;span class="s1"&gt;&amp;lt;/node&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In fact, I never understood what I was doing with the following values or why there'd be a &lt;code&gt;settings-daemon.plugins.color&lt;/code&gt; and a &lt;code&gt;SettingsDaemon/Color&lt;/code&gt;. Not only is one in kebab-case but one has a &lt;code&gt;plugins&lt;/code&gt; path while the other doesn't:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BUS_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;org.gnome.SettingsDaemon.Color&amp;#39;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OBJECT_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/org/gnome/SettingsDaemon/Color&amp;#39;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;COLOR_SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;org.gnome.settings-daemon.plugins.color&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Inspecting the &lt;code&gt;panels/display/cc-night-light-page.c&lt;/code&gt; file in the &lt;a href="https://gitlab.gnome.org/GNOME/gnome-control-center"&gt;gnome-control-center repository&lt;/a&gt;, we again see an instance of the &lt;code&gt;org.gnome.settings-daemon.plugins.color&lt;/code&gt; key being defined and used as a &lt;code&gt;GSetting&lt;/code&gt; instance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define DISPLAY_SCHEMA   &amp;quot;org.gnome.settings-daemon.plugins.color&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;// etc&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;settings_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g_settings_new&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DISPLAY_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is why we can use &lt;code&gt;dconf&lt;/code&gt; to inspect the values:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;[dafne@localhost]% &lt;/span&gt;dconf dump &lt;span class="s2"&gt;&amp;quot;/org/gnome/settings-daemon/plugins/color/&amp;quot;&lt;/span&gt;

&lt;span class="go"&gt;[/]&lt;/span&gt;
&lt;span class="go"&gt;night-light-enabled=&lt;/span&gt;
&lt;span class="go"&gt;night-light-last-coordinates=&lt;/span&gt;
&lt;span class="go"&gt;night-light-schedule-automatic=&lt;/span&gt;
&lt;span class="go"&gt;night-light-schedule-from=&lt;/span&gt;
&lt;span class="go"&gt;night-light-schedule-to=&lt;/span&gt;
&lt;span class="go"&gt;night-light-temperature=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here is where I realized that &lt;code&gt;/org/gnome/settings-daemon/plugins/color/&lt;/code&gt; is just our system settings and that &lt;code&gt;org.gnome.SettingsDaemon.Color&lt;/code&gt; was instead the path to the D-Bus object!&lt;/p&gt;
&lt;dl&gt;
    &lt;dt&gt;GSettings&lt;/dt&gt;
    &lt;dd&gt;
      An API for storing application settings
      &lt;ul&gt;
        &lt;li&gt;Uses &lt;a href="https://en.wikipedia.org/wiki/Dconf"&gt;dconf&lt;/a&gt; on GNU systems&lt;/a&gt;
        &lt;li&gt;Uses the &lt;a href="https://en.wikipedia.org/wiki/Windows_Registry"&gt;Registry&lt;/a&gt; on Windows&lt;/li&gt;
        &lt;li&gt;Uses &lt;a href="https://en.wikipedia.org/wiki/Property_list"&gt;property list mechanism&lt;/a&gt; on MacOS&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/dd&gt;
    &lt;dt&gt;dconf&lt;/dt&gt;
    &lt;dd&gt;A backend for GSettings on platforms that don't already have their own configuration storage systems&lt;/dd&gt;
    &lt;dt&gt;D-Bus&lt;/dt&gt;
    &lt;dd&gt;An interprocess communication (IPC) system, kind of like a global event bus&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;Using the &lt;a href="https://wiki.gnome.org/Apps/DFeet"&gt;D-Feet app&lt;/a&gt;, I'm now able to inspect the "Session Bus" for &lt;code&gt;org.gnome.SettingsDaemon.Color&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="D-Feet output for org.gnome.SettingsDaemon.Color" src="https://dontreinventbicycle.com/images/rewriting-night-light/dfeet.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Here, we can see the following properties for the interface listed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Boolean&lt;/code&gt; DisabledUntilTomorrow (read/write)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Boolean&lt;/code&gt; NightLightActive (read)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Double&lt;/code&gt; Sunrise (read)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Double&lt;/code&gt; Sunset (read)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UInt32&lt;/code&gt; Temperature (read/write)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More interestingly however we see the line &lt;code&gt;cmd: /usr/lib/gsd-color&lt;/code&gt;; &lt;code&gt;gsd&lt;/code&gt; standing for "GNOME Settings Daemon", I guessed. Next, I decided to clone and go through the &lt;a href="https://gitlab.gnome.org/GNOME/gnome-settings-daemon/"&gt;gnome-settings-daemon repository&lt;/a&gt; to see where &lt;code&gt;gsd-color&lt;/code&gt; came from.&lt;/p&gt;
&lt;p&gt;Looking at &lt;code&gt;plugins/meson.build&lt;/code&gt; and &lt;code&gt;plugins/color/meson.build&lt;/code&gt;, we now have a rough idea how &lt;code&gt;gsd-color&lt;/code&gt; can be built:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;executable(
  &amp;#39;gsd-&amp;#39; + plugin_name,
  sources,
  include_directories: [top_inc, common_inc],
  dependencies: deps,
  c_args: cflags,
  install: true,
  install_rpath: gsd_pkglibdir,
  install_dir: gsd_libexecdir
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally in &lt;code&gt;plugins/color/gsd-color-manager.c&lt;/code&gt;, we can see the D-Bus introspection XML that we have been looking for:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;gchar&lt;/span&gt; &lt;span class="n"&gt;introspection_xml&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&amp;lt;node&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;  &amp;lt;interface name=&amp;#39;org.gnome.SettingsDaemon.Color&amp;#39;&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;    &amp;lt;method name=&amp;#39;NightLightPreview&amp;#39;&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;      &amp;lt;arg type=&amp;#39;u&amp;#39; name=&amp;#39;duration&amp;#39; direction=&amp;#39;in&amp;#39;/&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;    &amp;lt;/method&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;    &amp;lt;property name=&amp;#39;NightLightActive&amp;#39; type=&amp;#39;b&amp;#39; access=&amp;#39;read&amp;#39;/&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;    &amp;lt;property name=&amp;#39;Temperature&amp;#39; type=&amp;#39;u&amp;#39; access=&amp;#39;readwrite&amp;#39;/&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;    &amp;lt;property name=&amp;#39;DisabledUntilTomorrow&amp;#39; type=&amp;#39;b&amp;#39; access=&amp;#39;readwrite&amp;#39;/&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;    &amp;lt;property name=&amp;#39;Sunrise&amp;#39; type=&amp;#39;d&amp;#39; access=&amp;#39;read&amp;#39;/&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;    &amp;lt;property name=&amp;#39;Sunset&amp;#39; type=&amp;#39;d&amp;#39; access=&amp;#39;read&amp;#39;/&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;  &amp;lt;/interface&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&amp;lt;/node&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Going back to the &lt;code&gt;loadInterfaceXML('org.gnome.SettingsDaemon.Color')&lt;/code&gt; call from &lt;a href="#similar-proxy"&gt;before&lt;/a&gt;, we see that it loads &lt;code&gt;data/dbus-interfaces/org.gnome.SettingsDaemon.Color.xml&lt;/code&gt; from the &lt;a href="https://gitlab.gnome.org/GNOME/gnome-shell"&gt;gnome-shell repository&lt;/a&gt; with the following content instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;node&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;interface&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;org.gnome.SettingsDaemon.Color&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;DisabledUntilTomorrow&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;access=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;readwrite&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;NightLightActive&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;access=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;read&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/interface&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/node&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can notice here that the &lt;code&gt;Temperature&lt;/code&gt; property is missing. This may hamper us from using the same &lt;code&gt;loadInterfaceXML&lt;/code&gt; call. To test my hypothesis, I wrote the following GJS script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env gjs&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BUS_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;org.gnome.SettingsDaemon.Color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OBJECT_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/org/gnome/SettingsDaemon/Color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ColorInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`&amp;lt;node&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;  &amp;lt;interface name=&amp;quot;org.gnome.SettingsDaemon.Color&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;    &amp;lt;property name=&amp;quot;DisabledUntilTomorrow&amp;quot; type=&amp;quot;b&amp;quot; access=&amp;quot;readwrite&amp;quot;/&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;    &amp;lt;property name=&amp;quot;NightLightActive&amp;quot; type=&amp;quot;b&amp;quot; access=&amp;quot;read&amp;quot;/&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;    &amp;lt;property name=&amp;#39;Temperature&amp;#39; type=&amp;#39;u&amp;#39; access=&amp;#39;readwrite&amp;#39;/&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;  &amp;lt;/interface&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;&amp;lt;/node&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ColorProxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DBusProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;makeProxyWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ColorInterface&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ColorProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BUS_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OBJECT_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Temperature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// I&amp;#39;d randomly change this value to test for changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As expected, the script did not do anything when the "Temperature" property was removed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Temperature&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;u&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;access=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;readwrite&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We would indeed need to define our own D-Bus introspection XML. Additionally, we observe that writing the values to D-Bus does not update the value in GSettings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gsettings get org.gnome.settings-daemon.plugins.color night-light-temperature
&lt;span class="c1"&gt;# uint32 3734&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the current version of the extension, we make changes to GSettings instead of writing to D-Bus. This is probably preferable because changes would then persist and also show in GNOME Settings. That variant would look like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env gjs&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;COLOR_SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;org.gnome.settings-daemon.plugins.color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;schema_id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;COLOR_SCHEMA&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set_uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;night-light-temperature&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Perhaps the deep-dive wasn't too necessary, but I now know just a tad more how everything works under the hood!.. and I also learned that the &lt;code&gt;Gio.Settings.schema&lt;/code&gt; property has been &lt;a href="https://gjs-docs.gnome.org/gio20~2.64p/gio.settings#property-schema"&gt;deprecated&lt;/a&gt; since GIO version &lt;code&gt;2.32&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Use the 'schema-id' property instead. In a future version, this property may instead refer to a Gio.SettingsSchema.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I had previously been using:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;COLOR_SCHEMA&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;when it should have been:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;- this._schema = new Gio.Settings({ schema: COLOR_SCHEMA });&lt;/span&gt;
&lt;span class="gi"&gt;+ this._schema = new Gio.Settings({ schema_id: COLOR_SCHEMA });&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;a minor caveat&lt;/h4&gt;
&lt;p&gt;While the &lt;code&gt;introspection_xml&lt;/code&gt; in &lt;code&gt;plugins/color/gsd-color-manager.c&lt;/code&gt; may specify &lt;code&gt;Temperature&lt;/code&gt; as being of type &lt;code&gt;u&lt;/code&gt;, I recall setting this as &lt;code&gt;d&lt;/code&gt; in my extension because of the following warning message:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Received property Temperature with type d does not match expected type u in the expected interface&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Investigating &lt;code&gt;plugins/color/gsd-color-manager.c&lt;/code&gt;, we see that the &lt;code&gt;on_temperature_notify&lt;/code&gt; function actually seems to emit a &lt;a href="https://developer.gnome.org/glib/stable/glib-GVariant.html"&gt;GVariant&lt;/a&gt; of type &lt;code&gt;double&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;emit_property_changed&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Temperature&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;g_variant_new_double&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The fix here is luckily quite simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;-&amp;lt;property name=&amp;#39;Temperature&amp;#39; type=&amp;#39;u&amp;#39; access=&amp;#39;readwrite&amp;#39;/&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+&amp;lt;property name=&amp;#39;Temperature&amp;#39; type=&amp;#39;d&amp;#39; access=&amp;#39;readwrite&amp;#39;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I'm not quite if this is the correct way about the issue, but I think I've jumped deep enough into the rabbit hole.&lt;/p&gt;
&lt;h2&gt;interlude&lt;/h2&gt;
&lt;p&gt;At this point, 2 months have passed since I last touched this project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;- Date: 2020-07-08&lt;/span&gt;
&lt;span class="gi"&gt;+ Date: 2020-09-06&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;My favourite earworm has now changed. It is now &lt;em&gt;circle the drain&lt;/em&gt; by &lt;em&gt;Soccer Mommy&lt;/em&gt; &lt;a href="https://sopharela.bandcamp.com/album/color-theory"&gt;♬&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;the rewrite, continued&lt;/h2&gt;
&lt;p&gt;Picking up from the D-Bus &amp;amp; GSettings investigation, we can now devise a basic system for how we should update the slider.&lt;/p&gt;
&lt;h3&gt;sinks (and sources)&lt;/h3&gt;
&lt;p&gt;Using the &lt;a href="https://en.wikipedia.org/wiki/Sink_(computing)"&gt;sinks analogy&lt;/a&gt; or better yet, the &lt;a href="https://cycle.js.org/#-dataflow"&gt;Cycle.js dataflow diagram&lt;/a&gt;;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We can treat D-Bus as the source&lt;ul&gt;
&lt;li&gt;Updates from the D-Bus proxy will notify the extension to update itself&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;We can treat GSettings as the sink&lt;ul&gt;
&lt;li&gt;Slider events will write to GSettings&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;application settings with gsettings&lt;/h3&gt;
&lt;p&gt;Since we're already touching GSettings, we might as well start messing with the extension settings. Referring to the &lt;a href="https://gjs-docs.gnome.org/gio20~2.64p/gio.settings"&gt;docs&lt;/a&gt;, we start with defining the schema. The extension already has a &lt;b id="settings-schema"&gt;settings schema&lt;/b&gt; of its own, and for the purpose of keeping backwards-compatibility no keys were renamed. I took to trying to better explain the purpose of each setting and added a new &lt;code&gt;swap-axis&lt;/code&gt; key to support the new axis-swapping feature.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;--- a/$extension/schemas/org.gnome.shell.extensions.nightlightslider.gschema.xml&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/$extension/schemas/org.gnome.shell.extensions.nightlightslider.gschema.xml&lt;/span&gt;
 &amp;lt;schemalist&amp;gt;
     &amp;lt;schema id=&amp;quot;org.gnome.shell.extensions.nightlightslider&amp;quot; path=&amp;quot;/org/gnome/shell/extensions/nightlightslider/&amp;quot;&amp;gt;
         &amp;lt;key name=&amp;quot;show-always&amp;quot; type=&amp;quot;b&amp;quot;&amp;gt;
             &amp;lt;default&amp;gt;false&amp;lt;/default&amp;gt;
             &amp;lt;summary&amp;gt;Show always&amp;lt;/summary&amp;gt;
&lt;span class="gd"&gt;-            &amp;lt;description&amp;gt;Show slider even when night light is off&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;description&amp;gt;Show the slider even when night light is disabled or off&amp;lt;/description&amp;gt;&lt;/span&gt;
         &amp;lt;/key&amp;gt;
         &amp;lt;key name=&amp;quot;show-status-icon&amp;quot; type=&amp;quot;b&amp;quot;&amp;gt;
             &amp;lt;default&amp;gt;true&amp;lt;/default&amp;gt;
&lt;span class="gd"&gt;-            &amp;lt;summary&amp;gt;Show status icon&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-            &amp;lt;description&amp;gt;Show status icon in status area&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;summary&amp;gt;Show indicator&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;description&amp;gt;Show the night light indicator in the status area&amp;lt;/description&amp;gt;&lt;/span&gt;
         &amp;lt;/key&amp;gt;
         &amp;lt;key name=&amp;quot;enable-always&amp;quot; type=&amp;quot;b&amp;quot;&amp;gt;
             &amp;lt;default&amp;gt;false&amp;lt;/default&amp;gt;
&lt;span class="gd"&gt;-            &amp;lt;summary&amp;gt;Enable always&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-            &amp;lt;description&amp;gt;Enable night light throughout the day&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;summary&amp;gt;Enable permanent night light&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;description&amp;gt;Constantly update the night light schedule such that it is enabled throughout the day&amp;lt;/description&amp;gt;&lt;/span&gt;
         &amp;lt;/key&amp;gt;
         &amp;lt;key name=&amp;quot;minimum&amp;quot; type=&amp;quot;i&amp;quot;&amp;gt;
             &amp;lt;default&amp;gt;1500&amp;lt;/default&amp;gt;
&lt;span class="gd"&gt;-            &amp;lt;summary&amp;gt;Minimum value&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-            &amp;lt;description&amp;gt;Minimum night light slider value&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;summary&amp;gt;Lowest temperature&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;description&amp;gt;Minimum slider value, lower is warmer&amp;lt;/description&amp;gt;&lt;/span&gt;
         &amp;lt;/key&amp;gt;
         &amp;lt;key name=&amp;quot;maximum&amp;quot; type=&amp;quot;i&amp;quot;&amp;gt;
             &amp;lt;default&amp;gt;5000&amp;lt;/default&amp;gt;
&lt;span class="gd"&gt;-            &amp;lt;summary&amp;gt;Maximum value&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-            &amp;lt;description&amp;gt;Maximum night light slider value&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;summary&amp;gt;Highest temperature&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;description&amp;gt;Maximum slider value, higher is cooler&amp;lt;/description&amp;gt;&lt;/span&gt;
         &amp;lt;/key&amp;gt;
         &amp;lt;key name=&amp;quot;brightness-sync&amp;quot; type=&amp;quot;b&amp;quot;&amp;gt;
             &amp;lt;default&amp;gt;false&amp;lt;/default&amp;gt;
&lt;span class="gd"&gt;-            &amp;lt;summary&amp;gt;Brightness sync&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-            &amp;lt;description&amp;gt;Sync brightness slider with night light slider&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;summary&amp;gt;Sync brightness percentage&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;description&amp;gt;Adjust both brightness and night light warmth&amp;lt;/description&amp;gt;&lt;/span&gt;
         &amp;lt;/key&amp;gt;
         &amp;lt;key name=&amp;quot;show-in-submenu&amp;quot; type=&amp;quot;b&amp;quot;&amp;gt;
             &amp;lt;default&amp;gt;false&amp;lt;/default&amp;gt;
             &amp;lt;summary&amp;gt;Show in submenu&amp;lt;/summary&amp;gt;
&lt;span class="gd"&gt;-            &amp;lt;description&amp;gt;Display slider in night light submenu&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;description&amp;gt;Display the slider in the night light submenu instead of at the panel menu&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+        &amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+        &amp;lt;key name=&amp;quot;swap-axis&amp;quot; type=&amp;quot;b&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;default&amp;gt;false&amp;lt;/default&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;summary&amp;gt;Swap slider axis&amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;description&amp;gt;Invert the slider axis such that lower is cooler and higher is warmer&amp;lt;/description&amp;gt;&lt;/span&gt;
         &amp;lt;/key&amp;gt;
     &amp;lt;/schema&amp;gt;
 &amp;lt;/schemalist&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;While we do have the &lt;code&gt;schema_id&lt;/code&gt; defined in the above XML as &lt;code&gt;org.gnome.shell.extensions.nightlightslider&lt;/code&gt;, we cannot simply initialize &lt;code&gt;Gio.Settings&lt;/code&gt; with this value as it is not defined anywhere in our &lt;code&gt;XDG_DATA_DIRS&lt;/code&gt; (usually &lt;code&gt;/usr/share/glib-2.0/schemas&lt;/code&gt;). You can read more about that under the &lt;a href="https://developer.gnome.org/gio/stable/glib-compile-schemas.html"&gt;glib-compile-schemas documentation&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EXTENSION_SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;org.gnome.shell.extensions.nightlightslider&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;schema_id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EXTENSION_SCHEMA&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In spite of the schema not being installed globally, we still observe that running &lt;code&gt;dconf dump "/org/gnome/shell/extensions/nightlightslider/"&lt;/code&gt; dumps the current extension settings (assuming you've used the extension before). That is because the settings are still stored in the local dconf database at &lt;code&gt;~/.config/dconf/user&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;refactoring the gsettings code&lt;/h4&gt;
&lt;p&gt;If you were to dig up through a lot of extensions, you might come across a file called &lt;code&gt;convenience.js&lt;/code&gt;, with a function that resembles the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Me&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;settings-schema&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schemaDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Me&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;schemas&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Attempting to load schema &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; from path &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;schemaDir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_path&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schemaSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schemaDir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SettingsSchemaSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;new_from_directory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;schemaDir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_path&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SettingsSchemaSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_default&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SettingsSchemaSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settingsSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schemaSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;settingsSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Schema &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; could not be loaded`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;settings_schema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;settingsSchema&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I think like many other extension writers we just copied from one another. Well, it turns out that this function has already been &lt;a href="https://web.archive.org/web/20200916121616/https://gitlab.gnome.org/GNOME/gnome-shell/-/commit/93425b05004094520790b12953bc3aa50f85367c"&gt;vendored&lt;/a&gt; in GNOME a whole 2 years ago! This is even stated in the &lt;a href="https://web.archive.org/web/20200707175025/https://wiki.gnome.org/Projects/GnomeShell/Extensions/Writing"&gt;official guide&lt;/a&gt;!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Long ago, Giovanni Campagna (aka gcampax) wrote a small helper script for Gettext translations and GSettings called &lt;code&gt;convenience.js&lt;/code&gt;. This script was used so widely by extension authors that they were merged in GNOME Shell in version 3.32.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Since we've already defined the &lt;code&gt;settings-schema&lt;/code&gt; key in the &lt;a href="#metadata-file"&gt;metadata.json&lt;/a&gt; and verified that this path is correct, reading our preferences is as simple as using the &lt;code&gt;ExtensionUtils.getSettings()&lt;/code&gt; utility!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ExtensionUtils&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;misc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extensionUtils&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;preferences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ExtensionUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Get value once&lt;/span&gt;
&lt;span class="nx"&gt;preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;show-in-submenu&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Listen to changes&lt;/span&gt;
&lt;span class="nx"&gt;preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;changed::show-in-submenu&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Recreate night light slider&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A special note on the &lt;code&gt;changed&lt;/code&gt; signal is that it supports listening to detailed connections such as &lt;code&gt;changed::show-in-submenu&lt;/code&gt; as shown above!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This signal supports detailed connections. You can connect to the detailed signal "changed::x" in order to only receive callbacks when key "x" changes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A caveat to note about detailed signals is that you must have read from them first:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that @settings only emits this signal if you have read key at least once while a signal handler was already connected for key.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;creating the slider&lt;/h3&gt;
&lt;p&gt;Finally revisiting the &lt;a href="#brightness-slider"&gt;brightness slider&lt;/a&gt;, we notice that it is an instance of &lt;code&gt;PanelMenu.SystemIndicator&lt;/code&gt;. Referring to &lt;code&gt;js/ui/panelMenu.js&lt;/code&gt;, we find the following information about the &lt;code&gt;SystemIndicator&lt;/code&gt; class:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This class manages one system indicator, which are the icons
that you see at the top right. A system indicator is composed
of an icon and a menu section, which will be composed into the
aggregate menu.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oddly enough however, there isn't any brightness indicator in GNOME as we see no instance of the &lt;code&gt;SystemIndicator._addIndicator()&lt;/code&gt; method being called. We do on the other hand see it in the night light indicator at &lt;code&gt;js/ui/status/nightLight.js&lt;/code&gt;. This is alright because we will be hijacking the night light indicator instead of managing our own instance.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_indicator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_addIndicator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_indicator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;icon_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;night-light-symbolic&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_indicator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Modifying the brightness slider slightly, we already have the base code for a bare-bones night light slider:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;--- brightness.js&lt;/span&gt;
&lt;span class="gi"&gt;+++ extension.js&lt;/span&gt;
 var Indicator = GObject.registerClass(
 class Indicator extends PanelMenu.SystemIndicator {
     _init() {
         super._init();
&lt;span class="gd"&gt;-        this._proxy = new BrightnessProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,&lt;/span&gt;
&lt;span class="gi"&gt;+        this._settings = new Gio.Settings({schema_id: COLOR_SCHEMA});&lt;/span&gt;
&lt;span class="gi"&gt;+        this._proxy = new ColorProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,&lt;/span&gt;
             (proxy, error) =&amp;gt; {
                 if (error) {
                     log(error.message);
                     return;
                 }
&lt;span class="gd"&gt;-&lt;/span&gt;
                 this._proxy.connect(&amp;#39;g-properties-changed&amp;#39;,
                     this._sync.bind(this));
                 this._sync();
             });

         this._item = new PopupMenu.PopupBaseMenuItem({activate: false});
         this.menu.addMenuItem(this._item);

         this._slider = new Slider.Slider(0);
         this._sliderChangedId = this._slider.connect(&amp;#39;notify::value&amp;#39;,
             this._sliderChanged.bind(this));
&lt;span class="gd"&gt;-        this._slider.accessible_name = _(&amp;#39;Brightness&amp;#39;);&lt;/span&gt;
&lt;span class="gi"&gt;+        this._slider.accessible_name = _(&amp;#39;Night Light Temperature&amp;#39;);&lt;/span&gt;

&lt;span class="gd"&gt;-        let icon = new St.Icon({icon_name: &amp;#39;display-brightness-symbolic&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+        this._slider_icon = new St.Icon({icon_name: &amp;#39;night-light-symbolic&amp;#39;,&lt;/span&gt;
             style_class: &amp;#39;popup-menu-icon&amp;#39;});
&lt;span class="gd"&gt;-        this._item.add(icon);&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        this._item.add(this._slider_icon);&lt;/span&gt;
         this._item.add_child(this._slider);
&lt;span class="gi"&gt;+&lt;/span&gt;
         this._item.connect(&amp;#39;button-press-event&amp;#39;, (actor, event) =&amp;gt; {
             return this._slider.startDragging(event);
         });
         this._item.connect(&amp;#39;key-press-event&amp;#39;, (actor, event) =&amp;gt; {
             return this._slider.emit(&amp;#39;key-press-event&amp;#39;, event);
         });
         this._item.connect(&amp;#39;scroll-event&amp;#39;, (actor, event) =&amp;gt; {
             return this._slider.emit(&amp;#39;scroll-event&amp;#39;, event);
         });
     }

     _sliderChanged() {
&lt;span class="gd"&gt;-        let percent = this._slider.value * 100;&lt;/span&gt;
&lt;span class="gd"&gt;-        this._proxy.Brightness = percent;&lt;/span&gt;
&lt;span class="gi"&gt;+        // TODO&lt;/span&gt;
     }

     _changeSlider(value) {
         this._slider.block_signal_handler(this._sliderChangedId);
         this._slider.value = value;
         this._slider.unblock_signal_handler(this._sliderChangedId);
     }

     _sync() {
&lt;span class="gd"&gt;-        let visible = this._proxy.Brightness &amp;gt;= 0;&lt;/span&gt;
&lt;span class="gd"&gt;-        this._item.visible = visible;&lt;/span&gt;
&lt;span class="gd"&gt;-        if (visible)&lt;/span&gt;
&lt;span class="gd"&gt;-            this._changeSlider(this._proxy.Brightness / 100.0);&lt;/span&gt;
&lt;span class="gi"&gt;+        // TODO&lt;/span&gt;
&lt;span class="gi"&gt;+        // You might want to hardcode this._item.visible = true for now&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+    destroy() {&lt;/span&gt;
&lt;span class="gi"&gt;+        this._item.destroy();&lt;/span&gt;
&lt;span class="gi"&gt;+        super.destroy();&lt;/span&gt;
     }
 });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;displaying the slider&lt;/h3&gt;
&lt;p&gt;With the minimal slider created, we can finally test displaying it with the extension. To do this, we will again need to refer to the &lt;code&gt;panel&lt;/code&gt; setup in the &lt;a href="https://gitlab.gnome.org/GNOME/gnome-shell"&gt;gnome-shell repository&lt;/a&gt;. Digging through &lt;code&gt;js/ui/main.js&lt;/code&gt;, we discover that the &lt;code&gt;panel&lt;/code&gt; instance is actually exported!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Panel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;panel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;_initializeUI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;panel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Panel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can verify that we do indeed have access to the &lt;code&gt;panel&lt;/code&gt; instance by testing for the value of &lt;code&gt;imports.ui.main.panel&lt;/code&gt; in the &lt;a href="https://wiki.gnome.org/Projects/GnomeShell/LookingGlass"&gt;Looking Glass&lt;/a&gt;. Look out for the red highlight around the panel.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Inspecting the panel instance in Looking Glass" src="https://dontreinventbicycle.com/images/rewriting-night-light/lg.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Recalling the &lt;em&gt;Show in submenu&lt;/em&gt; option, we can either show the slider alongside other slides such as volume &amp;amp; brightness or neatly tucked inside the night light menu.&lt;/p&gt;
&lt;p&gt;Without going into too much detail, we now explore the internals of &lt;code&gt;js/ui/panel.js&lt;/code&gt;. Here, we find that the night light and brightness indicator classes are both defined under the constructor of the &lt;code&gt;AggregateMenu&lt;/code&gt; class:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// etc..&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_brightness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Indicator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nightLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Indicator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Notice that order in which they&amp;#39;re added corresponds to how they are displayed?&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_indicators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// etc..&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_indicators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_volume&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_indicators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_power&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_indicators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PopupMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arrowIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;St&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Side&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BOTTOM&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMenuItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_volume&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMenuItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_brightness&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMenuItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;PopupMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PopupSeparatorMenuItem&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// etc..&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMenuItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_power&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMenuItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With both the &lt;code&gt;AggregateMenu.menu&lt;/code&gt; and &lt;code&gt;AggregateMenu._nightLight&lt;/code&gt; instances accessible under the class instance, and the class instance being defined at &lt;code&gt;imports.ui.main.panel.statusArea.aggregateMenu&lt;/code&gt;, we should have all that we need to display the night light slider.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; class Extension {
     constructor() {
&lt;span class="gi"&gt;+        this._preferences = ExtensionUtils.getSettings();&lt;/span&gt;
     }

     enable() {
&lt;span class="gi"&gt;+        this._nightLight = new Indicator();&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        this._preferences.connect(&amp;#39;changed::show-in-submenu&amp;#39;, () =&amp;gt; {&lt;/span&gt;
&lt;span class="gi"&gt;+            this._nightLight.destroy();&lt;/span&gt;
&lt;span class="gi"&gt;+            this._nightLight = this.createIndicator();&lt;/span&gt;
&lt;span class="gi"&gt;+            this._show();&lt;/span&gt;
&lt;span class="gi"&gt;+        });&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        this._show();&lt;/span&gt;
     }

     disable() {
&lt;span class="gi"&gt;+        this._nightLight.destroy();&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+    _show() {&lt;/span&gt;
&lt;span class="gi"&gt;+        if (this._preferences.get_boolean(&amp;#39;show-in-submenu&amp;#39;))&lt;/span&gt;
&lt;span class="gi"&gt;+            panel.statusArea.aggregateMenu._nightLight._item.menu.addMenuItem(this._nightLight.menu);&lt;/span&gt;
&lt;span class="gi"&gt;+        else&lt;/span&gt;
&lt;span class="gi"&gt;+            panel.statusArea.aggregateMenu.menu.addMenuItem(this._nightLight.menu, 2);&lt;/span&gt;
     }
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;notice that order in which they're added corresponds to how they are displayed?&lt;/h4&gt;
&lt;p&gt;With regards to that comment, you may have noticed an additional parameter to the &lt;code&gt;aggregateMenu.menu.addMenuItem&lt;/code&gt; call. That's because the type signature for the function actually allows us to redefine the position of items added. In this instance, we want it after the volume and brightness slider, hence the index of &lt;code&gt;2&lt;/code&gt;. Without this index, the slider would actually appear under the system menu.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;addMenuItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;menuItem&lt;/span&gt;: &lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;position?&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;indicator options&lt;/h3&gt;
&lt;p&gt;From the &lt;a href="#settings-schema"&gt;settings schema&lt;/a&gt;, we can identify the following keys as being those key to the indicator:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;minimum&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maximum&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;swap-axis&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;show-always&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;brightness-sync&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We pass these options as an object to the constructor,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; var Indicator = GObject.registerClass(
 class Indicator extends PanelMenu.SystemIndicator {
&lt;span class="gd"&gt;-    _init() {&lt;/span&gt;
&lt;span class="gi"&gt;+    _init(options) {&lt;/span&gt;
         super._init();
&lt;span class="gi"&gt;+        this._options = options;&lt;/span&gt;
         this._settings = new Gio.Settings({schema_id: COLOR_SCHEMA});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;which can can initialize by running,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Indicator&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;minimum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;maximum&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;maximum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;swapAxis&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;swap-axis&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;showAlways&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;show-always&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;brightnessSync&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;brightness-sync&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and update with the following new &lt;code&gt;Indicator&lt;/code&gt; method,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;updateOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;option&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;show-always&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;feature by feature&lt;/h3&gt;
&lt;p&gt;Now, speed-running development feature-by-feature:&lt;/p&gt;
&lt;h4&gt;changing the temperature&lt;/h4&gt;
&lt;p&gt;The value for &lt;code&gt;Slider.value&lt;/code&gt; is a percentage decimal, thus we can calculate the resulting temperature easily with the formula &lt;code&gt;percent * (max - min) + min&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;_sliderChanged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;swapAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;swapAxis&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maximum&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set_uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;night-light-temperature&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Even more easily with &lt;code&gt;Slider.value&lt;/code&gt; being a percentage decimal, we can swap the axis with &lt;code&gt;1 - percent&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;brightness sync&lt;/h4&gt;
&lt;p&gt;To sync the night light temperature with the system brightness, we set up a similar D-Bus proxy as defined in &lt;code&gt;js/ui/status/brightness.js&lt;/code&gt;. The only difference here is that we will be treating it as a write-only proxy and unlike the &lt;code&gt;ColorProxy&lt;/code&gt;, won't calling &lt;code&gt;this._sync()&lt;/code&gt; inside it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loadInterfaceXML&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;misc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fileUtils&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BRIGHTNESS_BUS_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;org.gnome.SettingsDaemon.Power&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BRIGHTNESS_OBJECT_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/org/gnome/SettingsDaemon/Power&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BrightnessInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;loadInterfaceXML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;org.gnome.SettingsDaemon.Power.Screen&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BrightnessProxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DBusProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;makeProxyWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BrightnessInterface&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Indicator._init&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_brightnessProxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BrightnessProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BRIGHTNESS_BUS_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BRIGHTNESS_OBJECT_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`BrightnessProxy: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Again, adding on to the &lt;code&gt;_sliderChanged&lt;/code&gt; method, making sure to only update the brightness if the key is defined (this is usually undefined on desktops without brightness controls):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; _sliderChanged() {
&lt;span class="gd"&gt;-    const {swapAxis, minimum, maximum} = this._options;&lt;/span&gt;
&lt;span class="gi"&gt;+    const {swapAxis, minimum, maximum, brightnessSync} = this._options;&lt;/span&gt;
     const percent = swapAxis
         ? 1 - this._slider.value
         : this._slider.value;
     const temperature = percent * (maximum - minimum) + minimum;
     this._settings.set_uint(&amp;#39;night-light-temperature&amp;#39;, temperature);

&lt;span class="gi"&gt;+    if (brightnessSync &amp;amp;&amp;amp; this._brightnessProxy.Brightness &amp;gt;= 0)&lt;/span&gt;
&lt;span class="gi"&gt;+        this._brightnessProxy.Brightness = this._slider.value * 100;&lt;/span&gt;
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;updating the proxy sink&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;_sync&lt;/code&gt; function acts as a sink for the &lt;code&gt;ColorProxy&lt;/code&gt; object such that the slider always reflects the current system state, such as when the night light is no longer active or if the temperature has changed externally. This was set up by listening to the &lt;code&gt;g-properties-changed&lt;/code&gt; signal on the &lt;code&gt;this._proxy&lt;/code&gt; object previously:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;g-properties-changed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;_sync&lt;/code&gt; function would also be responsible to ensure that the slider is hidden when the night light is no longer active (&lt;code&gt;NightLightActive: false&lt;/code&gt;) while still respecting the &lt;code&gt;show-always&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;It would also need to update the slider with the updated percentage. To do so, we simply invert the previous temperature calculation function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;temperature = (percent * (maximum - minimum)) + minimum
temperature - minimum = percent * (maximum - minimum)
(temperature - minimum) / (maximum - minimum) = percent
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Again, swapping the axis is as simple as doing &lt;code&gt;1 - percent&lt;/code&gt;. Putting that together:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;_sync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showAlways&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;swapAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NightLightActive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;showAlways&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Temperature&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maximum&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;swapAxis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_changeSlider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_changeSlider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;debouncing the _sync method&lt;/h4&gt;
&lt;p&gt;While the &lt;code&gt;_changeSlider&lt;/code&gt; method is already set up to temporarily block the &lt;code&gt;notify::value&lt;/code&gt; handler to avoid a recursive loop,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;_changeSlider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;block_signal_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_sliderChangedId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unblock_signal_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_sliderChangedId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;we still need a way to debounce the &lt;code&gt;_sync&lt;/code&gt; method, because GNOME updates the night light temperature gradually. Here we see the slider slowly moving along as I click on the slider:&lt;/p&gt;
&lt;p&gt;&lt;img alt="The slider slowly slides to the clicked position" src="https://dontreinventbicycle.com/images/rewriting-night-light/slow-sync.gif"&gt;&lt;/p&gt;
&lt;p&gt;What happens is that the &lt;code&gt;Temperature&lt;/code&gt; value in the proxy is slowly updated over time until it reaches the desired &lt;code&gt;night-light-temperature&lt;/code&gt; value, hence calling the &lt;code&gt;_sync&lt;/code&gt; method which slowly updates the slider with using the &lt;code&gt;_changeSlider&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;To do this, we vendor a &lt;code&gt;debounce&lt;/code&gt; function from a &lt;a href="/gjs-debounce.html"&gt;sibling article&lt;/a&gt; and rename the &lt;code&gt;_sync&lt;/code&gt; function to &lt;code&gt;__sync&lt;/code&gt; so we can wrap it accordingly in the constructor:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_sync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;feature checklist checkpoint&lt;/h3&gt;
&lt;p&gt;At this point, we already have a pretty functional slider and have delivered upon the following requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sliding should change the night light temperature;&lt;/li&gt;
&lt;li&gt;Ability to swap the axis of the slider;&lt;/li&gt;
&lt;li&gt;Ability to define where in the panel menu the slider would show;&lt;/li&gt;
&lt;li&gt;Ability to have the night light elements be shown or hidden when the night light is inactive;&lt;/li&gt;
&lt;li&gt;Ability to have the night light sync up with the system brightness;&lt;/li&gt;
&lt;li&gt;Ability to define the minimum and maximum temperature of the slider;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Where the undelivered requirements can basically be split into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Indicator features&lt;ul&gt;
&lt;li&gt;Scrolling on the indicator should change the night light temperature;&lt;/li&gt;
&lt;li&gt;Ability to toggle the visibility of the night light indicator in the status area;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Enabling a night light schedule&lt;ul&gt;
&lt;li&gt;Ability to have the extension enable the night light indefinitely;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;indicator features&lt;/h3&gt;
&lt;p&gt;Handling the indicator is a bit more tricky, because we will need to hijack the existing night light indicator created by GNOME. The issue here is that GNOME handles the indicator visibility independently. Revisiting &lt;code&gt;js/ui/status/nightLight.js&lt;/code&gt;, we see the indicator's visibility handled by the &lt;code&gt;_sync&lt;/code&gt; method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;_sync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NightLightActive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// etc&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_indicator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What we want to do here instead of to possibly have the indicator hidden, even when the night light is active. It should resemble the following logic table:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;D-Bus &lt;code&gt;NightLightActive&lt;/code&gt; value&lt;/th&gt;
&lt;th&gt;GSettings &lt;code&gt;show-status-icon&lt;/code&gt; value&lt;/th&gt;
&lt;th&gt;Indicator visibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So in code, all we need to do is &lt;code&gt;NightLightActive ∧ show-status-icon&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;First off however, we make the copy for the description more clear since even I was a bit confused at first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         &amp;lt;key name=&amp;quot;show-status-icon&amp;quot; type=&amp;quot;b&amp;quot;&amp;gt;
             &amp;lt;default&amp;gt;true&amp;lt;/default&amp;gt;
             &amp;lt;summary&amp;gt;Show indicator&amp;lt;/summary&amp;gt;
&lt;span class="gd"&gt;-            &amp;lt;description&amp;gt;Show the night light indicator in the status area&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;lt;description&amp;gt;Show the night light indicator in the status area when night light is enabled&amp;lt;/description&amp;gt;&lt;/span&gt;
         &amp;lt;/key&amp;gt;
         &amp;lt;key name=&amp;quot;enable-always&amp;quot; type=&amp;quot;b&amp;quot;&amp;gt;
             &amp;lt;default&amp;gt;false&amp;lt;/default&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we would need to update the constructor for the &lt;code&gt;Indicator&lt;/code&gt; such that we can receive the existing night light &lt;code&gt;SystemIndicator&lt;/code&gt; and add an additional key to the &lt;code&gt;options&lt;/code&gt; parameter for the new &lt;code&gt;showStatusIcon&lt;/code&gt; preference:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; var Indicator = GObject.registerClass(
 class Indicator extends PanelMenu.SystemIndicator {
&lt;span class="gd"&gt;-    _init(options) {&lt;/span&gt;
&lt;span class="gi"&gt;+    _init(indicator, options) {&lt;/span&gt;
         super._init();

         // Decorate _sync method
         this._sync = debounce(this.__sync.bind(this), 500);

&lt;span class="gi"&gt;+        // Hijacked indicator instance&lt;/span&gt;
&lt;span class="gi"&gt;+        this._indicator = indicator;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
         // Indicator options
         this._options = options;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Indicator&lt;/code&gt; instance now needs to be initialized with the additional &lt;code&gt;indicator&lt;/code&gt; parameter, which we hijack from the existing night light in the panel:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indicator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusArea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aggregateMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Indicator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;minimum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;maximum&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;maximum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;swapAxis&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;swap-axis&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;showAlways&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;show-always&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;showStatusIcon&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;show-status-icon&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;brightnessSync&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;brightness-sync&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To update the indicator visibility, we make some changes to the &lt;code&gt;__sync&lt;/code&gt; method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     __sync() {
&lt;span class="gd"&gt;-        const {showAlways, swapAxis, minimum, maximum} = this._options;&lt;/span&gt;
&lt;span class="gi"&gt;+        const {showAlways, showStatusIcon, swapAxis, minimum, maximum} = this._options;&lt;/span&gt;
         const active = this._proxy.NightLightActive;
         this._item.visible = active || showAlways;
&lt;span class="gi"&gt;+        this._indicator.visible = active &amp;amp;&amp;amp; showStatusIcon;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;...and that didn't work 🤔&lt;/p&gt;
&lt;p&gt;&lt;img alt="The night light indicator flickers as we update the temperature" src="https://dontreinventbicycle.com/images/rewriting-night-light/show-status-icon.gif"&gt;&lt;/p&gt;
&lt;p&gt;Here, we see the night light indicator flickering to turn on and off, as if our extension and the night light indicator are tugging amongst each other to handle the icon visibility. Then, I remembered &lt;a href="#pretty-terrible-code"&gt;the "hack" that caused me to rewrite this whole extension to begin with&lt;/a&gt;. With a tad more wisdom, I was able to improve the code to be a bit more legible and easy to follow.&lt;/p&gt;
&lt;p&gt;I store the visibility state in a separate &lt;code&gt;_indicator_visibility&lt;/code&gt; property, which in turn calls a new &lt;code&gt;_updateIndicatorVisibility&lt;/code&gt; method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gi"&gt;+    _updateIndicatorVisibility() {&lt;/span&gt;
&lt;span class="gi"&gt;+        this._indicator.visible = this._indicator_visibility;&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;

     __sync() {
         const {showAlways, showStatusIcon, swapAxis, minimum, maximum} = this._options;
         const active = this._proxy.NightLightActive;
         this._item.visible = active || showAlways;
&lt;span class="gd"&gt;-        this._indicator.visible = active &amp;amp;&amp;amp; showStatusIcon;&lt;/span&gt;
&lt;span class="gi"&gt;+        this._indicator_visibility = active &amp;amp;&amp;amp; showStatusIcon;&lt;/span&gt;
&lt;span class="gi"&gt;+        this._updateIndicatorVisibility();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I then make sure to listen to the indicator's &lt;code&gt;show&lt;/code&gt; signal so I can update its visibility when required:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         this._indicatorScrollId = this._indicator.connect(&amp;#39;scroll-event&amp;#39;, (actor, event) =&amp;gt; {
             return this._slider.emit(&amp;#39;scroll-event&amp;#39;, event);
         });

&lt;span class="gi"&gt;+        // Connect indicator signals to the slider&lt;/span&gt;
&lt;span class="gi"&gt;+        this._indicatorShowId = this._indicator.connect(&amp;#39;show&amp;#39;, () =&amp;gt; {&lt;/span&gt;
&lt;span class="gi"&gt;+            this._updateIndicatorVisibility();&lt;/span&gt;
&lt;span class="gi"&gt;+        });&lt;/span&gt;
     }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The same of course applies to the &lt;code&gt;updateOption&lt;/code&gt; method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     updateOption(option, value) {
         this._options[option] = value;
&lt;span class="gd"&gt;-        if (option === &amp;#39;showAlways&amp;#39;)&lt;/span&gt;
&lt;span class="gd"&gt;-            this._sync();&lt;/span&gt;
&lt;span class="gi"&gt;+        switch (option) {&lt;/span&gt;
&lt;span class="gi"&gt;+        case &amp;#39;showAlways&amp;#39;:&lt;/span&gt;
&lt;span class="gi"&gt;+            return this._sync();&lt;/span&gt;
&lt;span class="gi"&gt;+        case &amp;#39;showStatusIcon&amp;#39;:&lt;/span&gt;
&lt;span class="gi"&gt;+            return this._updateIndicatorVisibility();&lt;/span&gt;
&lt;span class="gi"&gt;+        }&lt;/span&gt;
     }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And finally of course we remember to properly disconnect the listener on &lt;code&gt;destroy&lt;/code&gt; such that the indicator does not propagate events to a destroyed slider instance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     destroy() {
&lt;span class="gi"&gt;+        this._indicator.disconnect(this._indicatorShowId);&lt;/span&gt;
         this._item.destroy();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Voila!&lt;/p&gt;
&lt;p&gt;Next, to allow scrolling on the indicator to mimic scrolling on the slider, all we need to do is hook up the &lt;code&gt;scroll-event&lt;/code&gt; between the two:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         // Connect indicator signals to the slider
         this._indicatorShowId = this._indicator.connect(&amp;#39;show&amp;#39;, () =&amp;gt; {
             this._updateIndicatorVisibility();
         });
&lt;span class="gi"&gt;+        this._indicatorScrollId = this._indicator.connect(&amp;#39;scroll-event&amp;#39;, (actor, event) =&amp;gt; {&lt;/span&gt;
&lt;span class="gi"&gt;+            return this._slider.emit(&amp;#39;scroll-event&amp;#39;, event);&lt;/span&gt;
&lt;span class="gi"&gt;+        });&lt;/span&gt;
     }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And again, we remember to disconnect the listener on &lt;code&gt;destroy&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     destroy() {
         this._indicator.disconnect(this._indicatorShowId);
&lt;span class="gi"&gt;+        this._indicator.disconnect(this._indicatorScrollId);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;enabling the night light indefinitely&lt;/h3&gt;
&lt;p&gt;Having the night light enabled indefinitely is as simple as having the night light schedule shifted indefinitely, changing the following values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;night-light-schedule-automatic&lt;/code&gt; to false&lt;/li&gt;
&lt;li&gt;&lt;code&gt;night-light-schedule-from&lt;/code&gt; to the current time + N hours&lt;/li&gt;
&lt;li&gt;&lt;code&gt;night-light-schedule-to&lt;/code&gt; to the current time - N hours&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I found &lt;code&gt;N: 6&lt;/code&gt; to be a good time range for the previous night light slider. In order to accomplish this, we need a &lt;code&gt;setInterval&lt;/code&gt;-like function to shift the night-light schedule every so often. Again here we vendor a &lt;code&gt;setInterval&lt;/code&gt; function from another &lt;a href="/gjs-set-timeout-interval.html"&gt;sibling article&lt;/a&gt;. From this, we can easily piece together the following class:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;NightLightSchedule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;enableTimer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;night-light-schedule-automatic&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Update schedule every 1 hour&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_updateSchedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_updateSchedule&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;disableTimer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;night-light-schedule-automatic&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;_updateSchedule&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Set a schedule span of 6 hours to &amp;amp; from now&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;6&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;6&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set_double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;night-light-schedule-to&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set_double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;night-light-schedule-from&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here, I removed some flawed logic in the original extension where I'd attempt to restore the original values of &lt;code&gt;night-light-schedule-{to,from,automatic}&lt;/code&gt; even if the code would technically not hold up across reboots.&lt;/p&gt;
&lt;h2&gt;completing the extension&lt;/h2&gt;
&lt;p&gt;With all the components ready, all that's left is to finish up the &lt;code&gt;Extension&lt;/code&gt; class. The class is set up to be idempotent and to dynamically react to preferences changes without needing the user to log in and out again for changes to take effect, unlike the previous version of the extension.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Extension&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Gio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;schema_id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;COLOR_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_scheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;NightLightSchedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ExtensionUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// We set up listeners for GSettings last because:&lt;/span&gt;
        &lt;span class="c1"&gt;// &amp;gt; Note that @settings only emits this signal if you have read key at&lt;/span&gt;
        &lt;span class="c1"&gt;// &amp;gt; least once while a signal handler was already connected for key.&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;changed::minimum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_updateOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;minimum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;minimum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;changed::maximum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_updateOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;maximum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;maximum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;changed::swap-axis&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_updateOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;swapAxis&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;swap-axis&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;changed::show-always&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_updateOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;showAlways&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;show-always&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;changed::show-status-icon&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_updateOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;showStatusIcon&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;show-status-icon&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;changed::brightness-sync&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_updateOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;brightnessSync&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;brightness-sync&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

        &lt;span class="c1"&gt;// Set up hook to recreate indicator on settings change&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;changed::show-in-submenu&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Set up hook to update scheduler&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;changed::enable-always&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_setupScheduler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;_setupScheduler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;enable-always&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enableTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disableTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;_create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indicator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusArea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aggregateMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Indicator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;minimum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nx"&gt;maximum&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;maximum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nx"&gt;swapAxis&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;swap-axis&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nx"&gt;showAlways&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;show-always&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nx"&gt;showStatusIcon&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;show-status-icon&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nx"&gt;brightnessSync&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;brightness-sync&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Assign slider to AggregateMenu, just like other indicators&lt;/span&gt;
        &lt;span class="c1"&gt;// This also makes it easier to debug the extension&lt;/span&gt;
        &lt;span class="nx"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusArea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aggregateMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLightSlider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;show-in-submenu&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nx"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusArea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aggregateMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMenuItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="nx"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusArea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aggregateMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMenuItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;_updateOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_setupScheduler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;disable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_nightLight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disableTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;bugs into a week of use&lt;/h3&gt;
&lt;p&gt;&lt;del&gt;Thus concludes the night light slider rewrite.&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;Alas, even the best developers write bugs. Terrible developers such as myself however even more.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;JS&lt;/span&gt; &lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Argument&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;instance&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;may&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="nb nb-Type"&gt;null&lt;/span&gt;
&lt;span class="n"&gt;_init&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;GObject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prototype&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;block_signal_handler&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;///&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gnome&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gjs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;overrides&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;GObject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;574&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;
&lt;span class="n"&gt;_changeSlider&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dafne&lt;/span&gt;&lt;span class="o"&gt;/.&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gnome&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;night&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;slider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timur&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;129&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;
&lt;span class="n"&gt;__sync&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dafne&lt;/span&gt;&lt;span class="o"&gt;/.&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gnome&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;night&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;slider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timur&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;
&lt;span class="n"&gt;debouncedFunc&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dafne&lt;/span&gt;&lt;span class="o"&gt;/.&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gnome&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;night&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;slider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timur&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;convenience&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="nb nb-Type"&gt;Object&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Gjs_ui_popupMenu_PopupBaseMenuItem&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x55ef4c229600&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;been&lt;/span&gt; &lt;span class="n"&gt;already&lt;/span&gt; &lt;span class="n"&gt;deallocated&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;impossible&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="n"&gt;property&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;might&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;caused&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="n"&gt;having&lt;/span&gt; &lt;span class="n"&gt;been&lt;/span&gt; &lt;span class="n"&gt;destroyed&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;something&lt;/span&gt; &lt;span class="n"&gt;such&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;vfuncs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="mh"&gt;0x55ef489dd8d0&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt;
&lt;span class="c1"&gt;#0   55ef530c7160 i   /home/dafne/.local/share/gnome-shell/extensions/night-light-slider.timur@linux.com/extension.js:141 (1b4d10eab3d0 @ 150)&lt;/span&gt;
&lt;span class="c1"&gt;#1   7ffd6c54c4a0 b   self-hosted:1007 (223d2c49e790 @ 492)&lt;/span&gt;
&lt;span class="c1"&gt;#2   55ef530c70c8 i   /home/dafne/.local/share/gnome-shell/extensions/night-light-slider.timur@linux.com/convenience.js:9 (1b4d10eabf10 @ 39)&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="mh"&gt;0x55ef489dd8d0&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt;
&lt;span class="c1"&gt;#0   55ef530c72d0 i   resource:///org/gnome/gjs/modules/core/overrides/GObject.js:574 (1dadea7b6cb8 @ 25)&lt;/span&gt;
&lt;span class="c1"&gt;#1   55ef530c7238 i   /home/dafne/.local/share/gnome-shell/extensions/night-light-slider.timur@linux.com/extension.js:129 (1b4d10eab2e0 @ 31)&lt;/span&gt;
&lt;span class="c1"&gt;#2   55ef530c7160 i   /home/dafne/.local/share/gnome-shell/extensions/night-light-slider.timur@linux.com/extension.js:150 (1b4d10eab3d0 @ 319)&lt;/span&gt;
&lt;span class="nb nb-Type"&gt;Object&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Gjs_ui_slider_Slider&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x55ef4c212210&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;been&lt;/span&gt; &lt;span class="n"&gt;already&lt;/span&gt; &lt;span class="n"&gt;deallocated&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;impossible&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;access&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;might&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;caused&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="n"&gt;having&lt;/span&gt; &lt;span class="n"&gt;been&lt;/span&gt; &lt;span class="n"&gt;destroyed&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;something&lt;/span&gt; &lt;span class="n"&gt;such&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;vfuncs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="c1"&gt;#3   7ffd6c54c4a0 b   self-hosted:1007 (223d2c49e790 @ 492)&lt;/span&gt;
&lt;span class="c1"&gt;#4   55ef530c70c8 i   /home/dafne/.local/share/gnome-shell/extensions/night-light-slider.timur@linux.com/convenience.js:9 (1b4d10eabf10 @ 39)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This was apparently caused because I had not properly disconnected the &lt;code&gt;g-properties-changed&lt;/code&gt; signal from the &lt;code&gt;ColorProxy&lt;/code&gt; when destroying the &lt;code&gt;Indicator&lt;/code&gt;. I also had to make sure I was removing all references to the indicator when the extension was disabled, such as at &lt;code&gt;panel.statusArea.aggregateMenu&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Also revising &lt;a href="https://wiki.gnome.org/Projects/GnomeShell/Extensions/Writing"&gt;the guide&lt;/a&gt; (on the 30th of September, I cannot for the life of me figure out how to resubmit a page to be re-archived 🤦), I come across the following comment that was not present &lt;a href="https://web.archive.org/web/20200707175025/https://wiki.gnome.org/Projects/GnomeShell/Extensions/Writing"&gt;before&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Because PanelMenu.Button is a ClutterActor, overriding the destroy()
method directly is bad idea. Instead PanelMenu.Button connects to
the signal, so we can override that callback and chain-up.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With &lt;code&gt;PanelMenu.SystemIndicator&lt;/code&gt; also being a &lt;code&gt;ClutterActor&lt;/code&gt;, I also followed suit to replace &lt;code&gt;destroy&lt;/code&gt; with a &lt;code&gt;_onDestroy&lt;/code&gt; method instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -111,6 +111,11 @@ class Indicator extends PanelMenu.SystemIndicator {&lt;/span&gt;
         this._indicatorScrollId = this._indicator.connect(&amp;#39;scroll-event&amp;#39;, (actor, event) =&amp;gt; {
             return this._slider.emit(&amp;#39;scroll-event&amp;#39;, event);
         });
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        // Because SystemIndicator is a ClutterActor, overriding the destroy()&lt;/span&gt;
&lt;span class="gi"&gt;+        // method directly is bad idea. Instead PanelMenu.Button connects to&lt;/span&gt;
&lt;span class="gi"&gt;+        // the signal, so we can override that callback and chain-up.&lt;/span&gt;
&lt;span class="gi"&gt;+        this.connect(&amp;#39;destroy&amp;#39;, this._onDestroy.bind(this));&lt;/span&gt;
     }

     _sliderChanged() {
&lt;span class="gu"&gt;@@ -161,11 +166,21 @@ class Indicator extends PanelMenu.SystemIndicator {&lt;/span&gt;
         }
     }

&lt;span class="gd"&gt;-    destroy() {&lt;/span&gt;
&lt;span class="gi"&gt;+    _onDestroy() {&lt;/span&gt;
&lt;span class="gi"&gt;+        // Unassign DBus proxies&lt;/span&gt;
&lt;span class="gi"&gt;+        this._proxy.disconnect(this._proxyChangedId);&lt;/span&gt;
&lt;span class="gi"&gt;+        this._proxy = null;&lt;/span&gt;
&lt;span class="gi"&gt;+        this._brightnessProxy = null;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        // Delete top-level items&lt;/span&gt;
&lt;span class="gi"&gt;+        this._item.destroy();&lt;/span&gt;
&lt;span class="gi"&gt;+        this._slider = null;&lt;/span&gt;
&lt;span class="gi"&gt;+        this._slider_icon = null;&lt;/span&gt;
&lt;span class="gi"&gt;+        this._item = null;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        // Disconnect external signals&lt;/span&gt;
         this._indicator.disconnect(this._indicatorShowId);
         this._indicator.disconnect(this._indicatorScrollId);
&lt;span class="gd"&gt;-        this._item.destroy();&lt;/span&gt;
&lt;span class="gd"&gt;-        super.destroy();&lt;/span&gt;
     }
 });

&lt;span class="gu"&gt;@@ -279,6 +294,7 @@ class Extension {&lt;/span&gt;
     disable() {
         this._nightLight.destroy();
         this._nightLight = null;
&lt;span class="gi"&gt;+        panel.statusArea.aggregateMenu._nightLightSlider = null;&lt;/span&gt;
         this._scheduler.disableTimer();
     }
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The next issue I found was that the slider would wiggle a little upon updates:&lt;/p&gt;
&lt;p&gt;&lt;img alt="The slider moves ever so slightly upon letting go of it" src="https://dontreinventbicycle.com/images/rewriting-night-light/slider-wiggle.gif"&gt;&lt;/p&gt;
&lt;p&gt;With some logging enabled, I noticed that the &lt;code&gt;__sync&lt;/code&gt; function was still being called by the &lt;code&gt;ColorProxy&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;_sliderChanged temperature: 2163.5719046208533
_sliderChanged temperature (uint): 2163
__sync temperature: 2181.3250236714307
__sync temperature: 2171.299908991444
...etc
_sliderChanged temperature: 3283.7099192831756
_sliderChanged temperature (uint): 3283
__sync temperature: 3302.9502637814326
__sync temperature: 3292.842247173721
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is expected because the debounced &lt;code&gt;_sync&lt;/code&gt; function should still proxy updates called after the 500ms interval to &lt;code&gt;__sync&lt;/code&gt;. What is observed however is that the final "Temperature" reported by the proxy will always fall within a delta (&lt;code&gt;2171 - 2163&lt;/code&gt;, &lt;code&gt;3292 - 3283&lt;/code&gt;) of the set value instead of actually reaching the intended value 🤔.&lt;/p&gt;
&lt;p&gt;Revisiting &lt;code id="gsd-night-light"&gt;plugins/color/gsd-night-light.c&lt;/code&gt; in the &lt;a href="https://gitlab.gnome.org/GNOME/gnome-settings-daemon/"&gt;gnome-settings-daemon repository&lt;/a&gt;, we see that this is intended behaviour where the proxy stops notifying of updates if they fall within the &lt;code&gt;GSD_TEMPERATURE_MAX_DELTA&lt;/code&gt; delta of &lt;code&gt;10.f&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="nf"&gt;gsd_night_light_set_temperature_internal&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GsdNightLight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gdouble&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cached_temperature&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;GSD_TEMPERATURE_MAX_DELTA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cached_temperature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;g_object_notify&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G_OBJECT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;temperature&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The following is a representation of how the "Temperature" value updates over time when the slider is moved where,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first &lt;code&gt;x&lt;/code&gt; is the initial D-Bus value&lt;/li&gt;
&lt;li&gt;The second &lt;code&gt;x&lt;/code&gt; is the first value the proxy reports&lt;/li&gt;
&lt;li&gt;The third &lt;code&gt;x&lt;/code&gt; is the final value the proxy reports&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="A line graph spanning from 3540 to 1818 with 3 cross marks at 3538, 3521, and 1820" src="https://dontreinventbicycle.com/images/rewriting-night-light/temperature-graph.svg"&gt;&lt;/p&gt;
&lt;!--
    # Code for the above graph
    import matplotlib.pyplot as plt
    import numpy as np
    initial_gsettings = 3540
    initial_dbus = 3538.605778952489
    intended_gsettings = 1818
    temperatures = [3521.2472754909486, 3486.815109868534, 3436.0434127289154, 3370.301985261057, 3291.3987858106343, 3201.499948253913, 3102.872727242798, 2998.226010997963, 2890.1942591264665, 2781.172903541932, 2673.426025610568, 2569.007934538799, 2469.663703748485, 2376.8587117047746, 2291.5387581344876, 2214.4505313280097, 2145.770472052127, 2085.687260144818, 2033.8907925183844, 1989.913881260548, 1953.1444933604507, 1922.8694509867412, 1898.3057326648855, 1878.5053263629798, 1862.967715866815, 1850.966246204948, 1840.8430899988189, 1830.8366744248362, 1820.7987570188332]
    gsettings_spread = [initial_gsettings, initial_dbus] + temperatures + [intended_gsettings]
    temperature_spread = [np.nan, np.nan] + temperatures + [np.nan]
    plt.plot(gsettings_spread, marker='x', markevery=[1])
    plt.plot(temperature_spread, marker='x', markevery=[2, 30])
    plt.show()
--&gt;

&lt;p&gt;The unmarked head and tail span of the line is the actual system GSettings value, showing that the value reported by D-Bus is never exactly the same as that value. One solution would be to duplicate the delta check with something like &lt;code class="highlight"&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Temperature&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get_uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;night-light-temperature&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;/code&gt;, but this feels like a hack.&lt;/p&gt;
&lt;p&gt;In the &lt;a href="#gsd-night-light"&gt;same file&lt;/a&gt;, we also see that the night light temperature is actually smeared over a span of &lt;code&gt;GSD_NIGHT_LIGHT_SMOOTH_SMEAR&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;gboolean&lt;/span&gt;
&lt;span class="nf"&gt;gsd_night_light_smooth_cb&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gpointer&lt;/span&gt; &lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;GsdNightLight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GSD_NIGHT_LIGHT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;gdouble&lt;/span&gt; &lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;gdouble&lt;/span&gt; &lt;span class="n"&gt;frac&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="cm"&gt;/* find fraction */&lt;/span&gt;
        &lt;span class="n"&gt;frac&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g_timer_elapsed&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;smooth_timer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;GSD_NIGHT_LIGHT_SMOOTH_SMEAR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frac&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;gsd_night_light_set_temperature_internal&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                          &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;smooth_target_temperature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;smooth_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;G_SOURCE_REMOVE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="cm"&gt;/* set new temperature step using log curve */&lt;/span&gt;
        &lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;smooth_target_temperature&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cached_temperature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="n"&gt;frac&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cached_temperature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;gsd_night_light_set_temperature_internal&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;G_SOURCE_CONTINUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Since we can identify the smear spread of &lt;code&gt;GSD_NIGHT_LIGHT_SMOOTH_SMEAR&lt;/code&gt;, all we have to do is ignore updates from &lt;code&gt;ColorProxy&lt;/code&gt; over that duration, like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -124,6 +124,13 @@ class Indicator extends PanelMenu.SystemIndicator {&lt;/span&gt;
             ? 1 - this._slider.value
             : this._slider.value;
         const temperature = percent * (maximum - minimum) + minimum;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        // Block updates from ColorProxy over the 5s smear duration&lt;/span&gt;
&lt;span class="gi"&gt;+        this._proxy.block_signal_handler(this._proxyChangedId);&lt;/span&gt;
&lt;span class="gi"&gt;+        GLib.timeout_add(GLib.PRIORITY_DEFAULT, 5000,&lt;/span&gt;
&lt;span class="gi"&gt;+            () =&amp;gt; this._proxy.unblock_signal_handler(this._proxyChangedId));&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        // Update GSettings&lt;/span&gt;
         this._settings.set_uint(&amp;#39;night-light-temperature&amp;#39;, temperature);

         if (brightnessSync &amp;amp;&amp;amp; this._brightnessProxy.Brightness &amp;gt;= 0)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A final bug I discovered while rewriting the preferences panel was that the &lt;code&gt;show-status-icon&lt;/code&gt; preference would not take effect immediately. This was luckily an easy fix:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -167,9 +167,8 @@ class Indicator extends PanelMenu.SystemIndicator {&lt;/span&gt;
         this._options[option] = value;
         switch (option) {
         case &amp;#39;showAlways&amp;#39;:
&lt;span class="gd"&gt;-            return this._sync();&lt;/span&gt;
         case &amp;#39;showStatusIcon&amp;#39;:
&lt;span class="gd"&gt;-            return this._updateIndicatorVisibility();&lt;/span&gt;
&lt;span class="gi"&gt;+            return this._sync();&lt;/span&gt;
         }
     }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Copy of Copy of Final final asdasda Copy (3).psd&lt;/h3&gt;
&lt;p&gt;Thus concludes the night light slider rewrite. With the &lt;a href="https://www.gnome.org/news/2020/09/gnome-3-38-released/"&gt;GNOME 3.38 release&lt;/a&gt; came the habitual &lt;a href="https://codeberg.org/kiyui/gnome-shell-night-light-slider-extension/issues/70"&gt;"Not working with $version" report&lt;/a&gt; and thus a rush for me to complete all bug fixes and the preferences panel.&lt;/p&gt;
&lt;p&gt;The PR for the entire rewrite (including the preferences panel) is available on &lt;del&gt;GitHub&lt;/del&gt; &lt;a href="https://codeberg.org/kiyui/gnome-shell-night-light-slider-extension/pulls/68"&gt;Codeberg&lt;/a&gt; or to download as a ZIP from &lt;a href="https://dontreinventbicycle.com/download/rewriting-night-light/night-light-slider.timur@linux.com.zip"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This article would be followed up by a write-up of the preferences panel rewrite.&lt;/p&gt;</content><category term="code"></category><category term="gnome-shell"></category><category term="gnome-shell-extension"></category><category term="gjs"></category><category term="gtk"></category></entry><entry><title>"debounce" in gjs</title><link href="https://dontreinventbicycle.com/gjs-debounce.html" rel="alternate"></link><published>2020-09-16T00:00:00+08:00</published><updated>2020-09-16T00:00:00+08:00</updated><author><name>Dafne Kiyui</name></author><id>tag:dontreinventbicycle.com,2020-09-16:/gjs-debounce.html</id><summary type="html">&lt;p&gt;The following code is licensed under the &lt;a href="https://www.gnu.org/licenses/lgpl-3.0.txt"&gt;GNU Lesser General Public License v3.0 or later&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* exported debounce */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**&lt;/span&gt;
&lt;span class="cm"&gt; * Copyright 2020 Dafne Kiyui&lt;/span&gt;
&lt;span class="cm"&gt; *&lt;/span&gt;
&lt;span class="cm"&gt; * This program is free software: you can redistribute it and/or modify&lt;/span&gt;
&lt;span class="cm"&gt; * it under the terms of the GNU Lesser General Public License as …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;The following code is licensed under the &lt;a href="https://www.gnu.org/licenses/lgpl-3.0.txt"&gt;GNU Lesser General Public License v3.0 or later&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* exported debounce */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**&lt;/span&gt;
&lt;span class="cm"&gt; * Copyright 2020 Dafne Kiyui&lt;/span&gt;
&lt;span class="cm"&gt; *&lt;/span&gt;
&lt;span class="cm"&gt; * This program is free software: you can redistribute it and/or modify&lt;/span&gt;
&lt;span class="cm"&gt; * it under the terms of the GNU Lesser General Public License as published by&lt;/span&gt;
&lt;span class="cm"&gt; * the Free Software Foundation, either version 3 of the License, or&lt;/span&gt;
&lt;span class="cm"&gt; * (at your option) any later version.&lt;/span&gt;
&lt;span class="cm"&gt; *&lt;/span&gt;
&lt;span class="cm"&gt; * This program is distributed in the hope that it will be useful,&lt;/span&gt;
&lt;span class="cm"&gt; * but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;/span&gt;
&lt;span class="cm"&gt; * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;/span&gt;
&lt;span class="cm"&gt; * GNU Lesser General Public License for more details.&lt;/span&gt;
&lt;span class="cm"&gt; *&lt;/span&gt;
&lt;span class="cm"&gt; * You should have received a copy of the GNU Lesser General Public License&lt;/span&gt;
&lt;span class="cm"&gt; * along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;

&lt;span class="cm"&gt;/**&lt;/span&gt;
&lt;span class="cm"&gt; * Decorate a function in a debounce wrapper&lt;/span&gt;
&lt;span class="cm"&gt; * @param {Function} func - The function to decorate&lt;/span&gt;
&lt;span class="cm"&gt; * @param {number} wait - Duration to wait in milliseconds&lt;/span&gt;
&lt;span class="cm"&gt; * @param {Object} options&lt;/span&gt;
&lt;span class="cm"&gt; * @param {number} options.priority - GLib.PRIORITY_{LOW,HIGH,..etc}&lt;/span&gt;
&lt;span class="cm"&gt; * @returns {Function} - A debounced variant of `func`&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PRIORITY_DEFAULT&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;debouncedFunc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;sourceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// It is a programmer error to attempt to remove a non-existent source&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;sourceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;debouncedFunc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="LGPL logo" src="https://dontreinventbicycle.com/images/lgplv3.png"&gt;&lt;/p&gt;
&lt;p&gt;A typical &lt;code&gt;debounce&lt;/code&gt; function in JS decorates a callback function in a &lt;code&gt;setTimeout&lt;/code&gt; block, running &lt;code&gt;clearTimeout&lt;/code&gt; and re-decorating the callback function as the decorated function is called over time. In GJS however, neither &lt;code&gt;setTimeout&lt;/code&gt; nor &lt;code&gt;clearTimeout&lt;/code&gt; are defined. Instead, &lt;code&gt;GLib.timeout_add&lt;/code&gt; is the GJS equivalent of &lt;code&gt;setTimeout&lt;/code&gt; and &lt;code&gt;setInterval&lt;/code&gt; in JavaScript. See the &lt;a href="/gjs-set-timeout-interval.html"&gt;setTimeout and setInterval in gjs&lt;/a&gt; article for more information.&lt;/p&gt;</content><category term="tips"></category><category term="gnome-shell"></category><category term="gjs"></category><category term="gtk"></category></entry></feed>