slightly nicer (so-called "human friendly") lastseen display
[bioacid.git] / notifyicon.d
blob1c985d3cba53fe37d1e9626d137e8fab1fe551fe
1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module notifyicon is aliced;
19 import std.datetime;
21 import arsd.color;
22 import arsd.image;
23 import arsd.simpledisplay;
25 import iv.cmdcon;
26 import iv.cmdcon.gl;
27 import iv.gxx;
28 import iv.meta;
29 import iv.nanovega;
30 import iv.nanovega.textlayouter;
31 import iv.strex;
32 import iv.tox;
33 import iv.txtser;
34 import iv.sdpyutil;
35 import iv.unarray;
36 import iv.utfutil;
37 import iv.vfs.io;
39 import accdb;
40 import fonts;
41 import icondata;
44 // ////////////////////////////////////////////////////////////////////////// //
45 __gshared SimpleWindow sdhint;
46 __gshared NVGContext hintvg;
47 __gshared string hinttext = "BioAcid";
48 __gshared Timer hintHideTimer;
49 __gshared int hintX, hintY;
52 // ////////////////////////////////////////////////////////////////////////// //
53 void setHint(T:const(char)[]) (T str) {
54 static if (is(T == typeof(null))) {
55 setHint("");
56 } else {
57 if (hinttext == str) return;
58 static if (is(T == string)) hinttext = str; else hinttext = str.idup;
59 if (sdhint is null || sdhint.closed) return;
60 if (sdhint.hidden) return;
61 createHintWindow(hintX, hintY);
66 // ////////////////////////////////////////////////////////////////////////// //
67 void createHintWindow (int x, int y) {
68 static GxRect getWorkArea () {
69 GxRect rc;
70 getWorkAreaRect(rc.x0, rc.y0, rc.width, rc.height);
71 return rc;
74 static GxSize hintWindowSize () {
75 laf.setFont("ui");
76 laf.setSize(18);
77 int textWidth = laf.textWidth(hinttext)+6;
78 if (textWidth < 8) textWidth = 8;
79 return GxSize(textWidth, laf.textHeight+4);
82 if (laf is null) return;
84 sdpyWindowClass = "BIOACID_HINT_WINDOW";
85 auto wsz = hintWindowSize();
87 auto wrc = getWorkArea();
88 int nx = x;
89 int ny = y;
90 if (nx+wsz.width > wrc.x1) nx = wrc.x1-wsz.width+1;
91 if (nx < wrc.x0) nx = wrc.x0;
92 if (ny+wsz.height > wrc.y1) ny = wrc.y1-wsz.height+1;
93 if (ny < wrc.y0) ny = wrc.y0;
94 hintX = nx;
95 hintY = ny;
98 if (sdhint is null || sdhint.closed) {
99 sdhint = new SimpleWindow(wsz.width, wsz.height, "AmperHint", OpenGlOptions.yes, Resizability.fixedSize, WindowTypes.undecorated, WindowFlags.skipTaskbar|WindowFlags.alwaysOnTop|WindowFlags.cannotBeActivated);
100 XSetWindowBackground(sdhint.impl.display, sdhint.impl.window, gxRGB!(255, 255, 0));
103 sdhint.onClosing = delegate () {
104 if (hintvg !is null) {
105 sdhint.setAsCurrentOpenGlContext();
106 scope(exit) { flushGui(); sdhint.releaseCurrentOpenGlContext(); }
107 hintvg.kill();
111 sdhint.windowResized = delegate (int wdt, int hgt) {
112 if (sdhint.closed) return;
113 if (!sdhint.visible) return;
114 sdhint.redrawOpenGlSceneNow();
117 sdhint.visibleForTheFirstTime = delegate () {
118 sdhint.setAsCurrentOpenGlContext(); // make this window active
119 scope(exit) sdhint.releaseCurrentOpenGlContext();
120 sdhint.vsync = false;
121 hintvg = nvgCreateContext(NVGContextFlag.FontNoAA);
122 if (hintvg is null) assert(0, "cannot initialize NanoVG");
123 //hintvg.createFont("ui", uiFontNames[0]);
124 //if (nvg !is null) hintvg.addFontsFrom(nvg); else hintvg.createFont("ui", uiFontNames[0]);
125 hintvg.fonsContext.fonsAddStashFonts(fstash);
126 //conwriteln("FIRST TIME");
127 //sdhint.redrawOpenGlSceneNow();
130 sdhint.redrawOpenGlScene = delegate () {
131 if (sdhint is null || sdhint.closed) return;
132 if (hintvg !is null) {
133 sdhint.setAsCurrentOpenGlContext(); // make this window active
134 scope(exit) sdhint.releaseCurrentOpenGlContext();
136 glClearColor(0, 0, 0, 0);
137 glClear(glNVGClearFlags/*|GL_COLOR_BUFFER_BIT*/);
139 hintvg.beginFrame(sdhint.width, sdhint.height);
140 scope(exit) hintvg.endFrame();
141 //hintvg.shapeAntiAlias = false;
143 hintvg.newPath();
144 hintvg.rect(0.5, 0.5, hintvg.width-1, hintvg.height-1);
145 hintvg.strokeWidth = 1;
146 hintvg.strokeColor(NVGColor.black);
147 hintvg.fillColor(NVGColor.yellow);
148 hintvg.fill();
149 hintvg.stroke();
151 hintvg.fontFace = "ui";
152 hintvg.fontSize = 18;
153 hintvg.textAlign = NVGTextAlign.H.Left;
154 hintvg.textAlign = NVGTextAlign.V.Baseline;
155 hintvg.fillColor(NVGColor.black);
156 hintvg.text(3, 2+hintvg.textFontAscender, hinttext);
158 flushGui();
161 // sorry for this hack
162 sdhint.setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_DOCK", true)(sdhint.display));
163 //sdhint.setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_TOOLTIP", true)(sdhint.display));
165 Atom[4] atoms;
166 atoms[0] = GetAtom!("_NET_WM_STATE_STICKY", true)(sdhint.impl.display);
167 atoms[1] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(sdhint.impl.display);
168 atoms[2] = GetAtom!("_NET_WM_STATE_SKIP_PAGER", true)(sdhint.impl.display);
169 atoms[3] = GetAtom!("_NET_WM_STATE_ABOVE", true)(sdhint.impl.display);
170 XChangeProperty(
171 sdhint.impl.display,
172 sdhint.impl.window,
173 GetAtom!("_NET_WM_STATE", true)(sdhint.impl.display),
174 XA_ATOM,
175 32 /* bits */,
176 0 /*PropModeReplace*/,
177 atoms.ptr,
178 cast(int)atoms.length);
180 if (sdhint.hidden) sdhint.show();
181 sdhint.moveResize(hintX, hintY, wsz.width, wsz.height);
182 flushGui();
186 // ////////////////////////////////////////////////////////////////////////// //
187 __gshared NotificationAreaIcon trayicon;
188 __gshared Image[6] trayimage; // offline, online, away; same as Status
189 __gshared ContactStatus traystatus = ContactStatus.Offline;
190 __gshared bool trayIsUnread = false;
191 __gshared bool trayBlinkStatus = true; // true: show kitty
192 __gshared Timer blinkTimer;
195 void setTrayUnread () {
196 if (!trayIsUnread) {
197 trayIsUnread = true;
198 if (blinkTimer is null) {
199 blinkTimer = new Timer(800, delegate () {
200 trayBlinkStatus = !trayBlinkStatus;
201 if (trayIsUnread) {
202 trayicon.icon = (trayBlinkStatus ? trayimage[$-1] : trayimage[traystatus]);
203 flushGui(); // or it may not redraw itself
207 if (trayBlinkStatus) {
208 trayicon.icon = trayimage[$-1];
209 flushGui(); // or it may not redraw itself
215 void setTrayStatus (ContactStatus status) {
216 if (status != traystatus || trayIsUnread) {
217 trayIsUnread = false;
218 traystatus = status;
219 trayicon.icon = trayimage[traystatus];
220 flushGui(); // or it may not redraw itself
225 void prepareTrayIcon () {
226 static Image buildTrayIcon (const(uint)[] img) {
227 auto icon = new TrueColorImage(16, 16);
228 scope(exit) { delete icon.imageData.bytes; delete icon; }
229 foreach (immutable y; 0..16) {
230 foreach (immutable x; 0..16) {
231 Color c;
232 c.asUint = img[y*16+x];
233 if (c.a < 0x20) c = Color.black;
234 else if (c.a < 0xff) {
235 c = c.toBW;
236 c = Color(c.r*c.a/255, c.g*c.a/255, c.b*c.a/255);
238 icon.setPixel(x, y, c);
241 return Image.fromMemoryImage(icon);
244 trayimage[ContactStatus.Offline] = buildTrayIcon(baph16Gray[]);
245 trayimage[ContactStatus.Online] = buildTrayIcon(baph16Online[]);
246 trayimage[ContactStatus.Away] = buildTrayIcon(baph16Away[]);
247 trayimage[ContactStatus.Busy] = buildTrayIcon(baph16Busy[]);
248 trayimage[ContactStatus.Connecting] = buildTrayIcon(baph16Orange[]);
249 trayimage[$-1] = buildTrayIcon(kittyMessage[]);
250 //traystatus = Contact.Status.Online;
251 trayicon = new NotificationAreaIcon("BioAcid", trayimage[traystatus], (int x, int y, MouseButton button, ModifierState mods) {
252 //conwritefln!"x=%d; y=%d; button=%u; mods=0x%04x"(x, y, button, mods);
253 if (button == MouseButton.middle) {
254 concmd("quit");
255 glconPostDoConCommands!true();
256 return;
258 if (button == MouseButton.left) {
259 concmd("win_toggle");
260 glconPostDoConCommands!true();
261 return;
264 trayicon.onEnter = delegate (int x, int y, ModifierState mods) {
265 if (sdhint is null || sdhint.hidden) {
266 createHintWindow(x+18, y+2);
267 if (hintHideTimer !is null) hintHideTimer.destroy();
268 if (sdhint !is null) {
269 hintHideTimer = new Timer(3000, delegate () {
270 if (hintHideTimer !is null) {
271 hintHideTimer.destroy();
272 hintHideTimer = null;
273 if (sdhint !is null && !sdhint.closed) {
274 //sdhint.close();
275 //sdhint = null;
276 sdhint.hide();
277 flushGui();
284 trayicon.onLeave = delegate () {