| 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 | | |
| | 114 | class BorderColorManager: |
| | 115 | """Base class for border color manager objects. |
| | 116 | |
| | 117 | It should be subclassed by actual managers, which must implement |
| | 118 | the set_client_border_colors() method. |
| | 119 | """ |
| | 120 | |
| | 121 | def set_client_border_colors(self, client): |
| | 122 | """Override this to implement the border color selection for client. |
| | 123 | """ |
| | 124 | raise NotImplementedError('%s.set_client_border_colors()' % self.__class__) |
| | 125 | |
| | 126 | |
| | 127 | class FixedBorderColor(BorderColorManager): |
| | 128 | """Use fixed border colors, one for non-focused and one for focused clients. |
| | 129 | """ |
| | 130 | |
| | 131 | def __init__(self, blurred, focused): |
| | 132 | """Set the color of all clients to blurred and focused, resp. |
| | 133 | |
| | 134 | They can either be strings naming a color, or three-tuples |
| | 135 | specifying a color as an (r, g, b) value in the range [0, 65535]. |
| | 136 | """ |
| | 137 | self.blurred_color = blurred |
| | 138 | self.focused_color = focused |
| | 139 | |
| | 140 | def set_client_border_colors(self, client): |
| | 141 | # The rdb stuff should be removed, but maybe someone is still using it... |
| | 142 | |
| | 143 | resname = client.wm.rdb_get('.border.color', '.Border.Color', '#000000') |
| | 144 | if self.blurred_color is not None: |
| | 145 | blurred = client.screen.get_color(self.blurred_color, default = resname) |
| | 146 | else: |
| | 147 | blurred = client.screen.get_color(resname) |
| | 148 | |
| | 149 | resname = client.wm.rdb_get('.border.focus.color', |
| | 150 | '.Border.Focus.Color', '#000000') |
| | 151 | if self.focused_color is not None: |
| | 152 | focused = client.screen.get_color(self.focused_color, default = resname) |
| | 153 | else: |
| | 154 | focused = client.screen.get_color(resname) |
| | 155 | |
| | 156 | client.border_set_colors(blurred, focused) |
| | 157 | |
| | 158 | |
| | 159 | class TitleBorderColor(BorderColorManager): |
| | 160 | """Change the color of the border based on the client title. |
| | 161 | |
| | 162 | The hue will be the same for a client (as long as the title |
| | 163 | doesn't change) but the saturation and brightness is changed |
| | 164 | depending on the client is focused or not, using an HSV-to-RGB translation. |
| | 165 | |
| 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): |
| | 178 | class TitleUpdater: |
| | 179 | def __init__(self, manager, client): |
| | 180 | self.manager = manager |
| | 181 | self.client = client |
| | 182 | |
| | 183 | def __call__(self, event): |
| | 184 | if event.atom == Xatom.WM_NAME: |
| | 185 | self.manager.choose_color(self.client) |
| | 186 | |
| | 187 | def __init__(self, blurred_saturation = 0.3, |
| | 188 | blurred_brightness = 0.7, |
| | 189 | focused_saturation = 1.0, |
| | 190 | focused_brightness = 0.7): |
| | 191 | |
| | 192 | """The saturation and brightness values should all be in the |
| | 193 | range [0.0, 1.0]. |
| | 194 | |
| | 195 | As noted above, the two brightness values should typically be |
| | 196 | the same, otherwise the color will be perceived to change in |
| | 197 | hue when the window is focused. |
| | 198 | """ |
| | 199 | |
| | 200 | self.blurred_saturation = blurred_saturation |
| | 201 | self.blurred_brightness = blurred_brightness |
| | 202 | self.focused_saturation = focused_saturation |
| | 203 | self.focused_brightness = focused_brightness |
| | 204 | |
| | 205 | def set_client_border_colors(self, client): |
| | 206 | # Add a dispatcher that updates the colors when the title changes |
| | 207 | client.dispatch.add_handler(X.PropertyNotify, self.TitleUpdater(self, client)) |
| | 208 | |
| | 209 | # And set the initial colors |
| | 210 | self.choose_color(client) |
| | 211 | |
| | 212 | def choose_color(self, client): |
| | 213 | |
| | 214 | # FIXME: release old colors first to work well in PseudoColor |
| | 215 | # visuals. Would require extensions to the color module, |
| | 216 | # though, reference counting and so on, and also an event |
| | 217 | # handler to free colors when the client is closed. |
| | 218 | |
| | 219 | hue = self.get_hue(client) |
| | 220 | |
| | 221 | blurred = self.get_color_hsv(client, hue, |
| | 222 | self.blurred_saturation, |
| | 223 | self.blurred_brightness) |
| | 224 | |
| | 225 | focused = self.get_color_hsv(client, hue, |
| | 226 | self.focused_saturation, |
| | 227 | self.focused_brightness) |
| | 228 | |
| | 229 | client.border_set_colors(blurred, focused) |
| | 230 | |
| | 231 | |
| | 232 | def get_hue(self, client): |
| 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): |
| | 246 | |
| | 247 | def get_color_hsv(self, client, 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)) |
| | 250 | return client.screen.get_color((r * 65535, g * 65535, b * 65535)) |
| | 251 | |