| | 72 | |
| | 73 | class BorderClient(BorderClientBase): |
| | 74 | """Set border color to one or another if the client is focused or not.""" |
| | 75 | border_color_name = None |
| | 76 | border_focuscolor_name = None |
| | 77 | |
| | 78 | def border_set_colors(self): |
| | 79 | resname = self.wm.rdb_get('.border.color', '.Border.Color', '#000000') |
| | 80 | if self.border_color_name is not None: |
| | 81 | self.border_color = self.screen.get_color( |
| | 82 | self.border_color_name, default = resname) |
| | 83 | else: |
| | 84 | self.border_color = self.screen.get_color(resname) |
| | 85 | |
| | 86 | resname = self.wm.rdb_get('.border.focus.color', |
| | 87 | '.Border.Focus.Color', '#000000') |
| | 88 | if self.border_focuscolor_name is not None: |
| | 89 | self.border_focuscolor = self.screen.get_color( |
| | 90 | self.border_focuscolor_name, default = resname) |
| | 91 | else: |
| | 92 | self.border_focuscolor = self.screen.get_color(resname) |
| | 93 | |
| | 94 | |
| | 95 | class DynamicColorBorderClient(BorderClientBase): |
| | 96 | |
| | 97 | """Change the color of the border based on some client property, |
| | 98 | by default the window title. |
| | 99 | |
| | 100 | Obviously this class shouldn't be used with palette-based visuals. |
| | 101 | |
| | 102 | |
| | 103 | Subclass and override border_dynamic_install_handler() and |
| | 104 | border_dynamic_get_hue() to change this behaviour. |
| | 105 | |
| | 106 | The hue will be the same for a client (as long as the title or |
| | 107 | whatever doesn't change) but the saturation and brightness is |
| | 108 | changed depending on the client is focused or not. This is |
| | 109 | controlled with four attributes, in the range [0.0, 1.0]: |
| | 110 | |
| | 111 | Non-focused: |
| | 112 | border_dynamic_saturation |
| | 113 | border_dynamic_brightness |
| | 114 | |
| | 115 | Focused: |
| | 116 | border_dynamic_focus_saturation |
| | 117 | border_dynamic_focus_brightness |
| | 118 | |
| | 119 | |
| | 120 | However, experimentation shows that HSV isn't very good, as |
| | 121 | confirmed by |
| | 122 | http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC36 |
| | 123 | |
| | 124 | Varying the brightness changes the perceived color, only changing |
| | 125 | the saturation works reasonably well. This might be a problem |
| | 126 | with the colorsys module, too. |
| | 127 | |
| | 128 | If anyone manages to understand it, maybe the CIE XYZ model should |
| | 129 | be tried instead. |
| | 130 | """ |
| | 131 | |
| | 132 | border_dynamic_saturation = 0.3 |
| | 133 | border_dynamic_brightness = 0.7 |
| | 134 | |
| | 135 | border_dynamic_focus_saturation = 1.0 |
| | 136 | border_dynamic_focus_brightness = 0.7 |
| | 137 | |
| | 138 | def __client_init__(self): |
| | 139 | # FIXME: check visual, and scream if it isn't suitable for |
| | 140 | # this kind of color-grabbing. Even better: fall back on |
| | 141 | # default colors. |
| | 142 | |
| | 143 | BorderClientBase.__client_init__(self) |
| | 144 | self.border_dynamic_install_handler() |
| | 145 | |
| | 146 | # |
| | 147 | # Override these methods to change color property |
| | 148 | # |
| | 149 | |
| | 150 | def border_dynamic_install_handler(self): |
| | 151 | self.dispatch.add_handler(X.PropertyNotify, self.border_dynamic_property_notify) |
| | 152 | |
| | 153 | def border_dynamic_get_hue(self): |
| | 154 | """Return the hue for this client, in the range [0.0, 1.0]""" |
| | 155 | |
| | 156 | # We can't really know the range of hash, but there's probably |
| | 157 | # at least 24 bits. Xor them into eight bits and use that for hue |
| | 158 | |
| | 159 | th = hash(self.get_title()) |
| | 160 | |
| | 161 | h = th & 0xff |
| | 162 | h ^= (th >> 8) & 0xff |
| | 163 | h ^= (th >> 16) & 0xff |
| | 164 | |
| | 165 | return float(h) / 0xff |
| | 166 | |
| | 167 | # |
| | 168 | # Internalish methods |
| | 169 | # |
| | 170 | |
| | 171 | def border_dynamic_property_notify(self, event): |
| | 172 | if event.atom == Xatom.WM_NAME: |
| | 173 | self.border_set_colors() |
| | 174 | self.border_update() |
| | 175 | |
| | 176 | def border_set_colors(self): |
| | 177 | hue = self.border_dynamic_get_hue() |
| | 178 | |
| | 179 | self.border_color = self.border_dynamic_get_color_hsv( |
| | 180 | hue, self.border_dynamic_saturation, |
| | 181 | self.border_dynamic_brightness) |
| | 182 | |
| | 183 | self.border_focuscolor = self.border_dynamic_get_color_hsv( |
| | 184 | hue, self.border_dynamic_focus_saturation, |
| | 185 | self.border_dynamic_focus_brightness) |
| | 186 | |
| | 187 | |
| | 188 | def border_dynamic_get_color_hsv(self, hue, saturation, brightness): |
| | 189 | import colorsys |
| | 190 | r, g, b = colorsys.hsv_to_rgb(hue, saturation, brightness) |
| | 191 | return self.screen.get_color((r * 65535, g * 65535, b * 65535)) |
| | 192 | |
| | 193 | # |
| | 194 | # I tested using chroma instead of HSV. Didn't really work that |
| | 195 | # much better, but I keep the code here if anyone want's to experiment with it |
| | 196 | # |
| | 197 | |
| | 198 | border_dynamic_luminance = 0.3 |
| | 199 | border_dynamic_focus_luminance = 0.7 |
| | 200 | |
| | 201 | def border_dynamic_get_chroma(self): |
| | 202 | """Return the Cr, Cb for this client, in the range [0, 255]""" |
| | 203 | |
| | 204 | h = hash(self.get_title()) |
| | 205 | return h & 0xff, (h >> 8) & 0xff |
| | 206 | |
| | 207 | def border_set_colors_chroma(self): |
| | 208 | cr, cb = self.border_dynamic_get_chroma() |
| | 209 | |
| | 210 | self.border_color = self.border_dynamic_get_color( |
| | 211 | self.border_dynamic_luminance, cr, cb) |
| | 212 | self.border_focuscolor = self.border_dynamic_get_color( |
| | 213 | self.border_dynamic_focus_luminance, cr, cb) |
| | 214 | |
| | 215 | def border_dynamic_get_chroma_color(self, y, cb, cr): |
| | 216 | # See http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC30 |
| | 217 | |
| | 218 | # scale [0.0, 1.0] to [16, 235] => [0, 219] |
| | 219 | y *= 219 |
| | 220 | |
| | 221 | # get cr and cb in range [-112, +112] |
| | 222 | cb = max(min(cb - 128, 112), -112) |
| | 223 | cr = max(min(cr - 128, 112), -112) |
| | 224 | |
| | 225 | # Do the matrix multiplication, but skip the divide-by-256 as |
| | 226 | # we actually want the range [0, 65535] |
| | 227 | |
| | 228 | r = (298.082 * y + 408.583 * cr) |
| | 229 | g = (298.082 * y - 100.291 * cb - 208.120 * cr) |
| | 230 | b = (298.082 * y + 516.411 * cb) |
| | 231 | |
| | 232 | # clamp into range |
| | 233 | r = min(max(r, 0), 65535) |
| | 234 | g = min(max(g, 0), 65535) |
| | 235 | b = min(max(b, 0), 65535) |
| | 236 | |
| | 237 | # and return the color |
| | 238 | return self.screen.get_color((r, g, b)) |