more fixes to window positions
[bioacid.git] / notifyicon.d
blobf06833cc8d347ebddd2798cec1449597b64074ca
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, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module notifyicon is aliced;
18 import std.datetime;
20 import arsd.color;
21 import arsd.image;
22 import arsd.simpledisplay;
24 import iv.cmdcon;
25 import iv.cmdcon.gl;
26 import iv.gxx;
27 import iv.meta;
28 import iv.nanovega;
29 import iv.nanovega.textlayouter;
30 import iv.strex;
31 import iv.tox;
32 import iv.sdpyutil;
33 import iv.unarray;
34 import iv.utfutil;
35 import iv.vfs.io;
37 import accdb;
38 import fonts;
39 import icondata;
42 // ////////////////////////////////////////////////////////////////////////// //
43 __gshared SimpleWindow sdhint;
44 __gshared string hinttext = "BioAcid";
45 __gshared Timer hintHideTimer;
46 __gshared int hintX, hintY;
49 // ////////////////////////////////////////////////////////////////////////// //
50 void setHint(T:const(char)[]) (T str) {
51 static if (is(T == typeof(null))) {
52 setHint("");
53 } else {
54 if (hinttext == str) return;
55 static if (is(T == string)) hinttext = str; else hinttext = str.idup;
56 if (sdhint is null || sdhint.closed) return;
57 if (sdhint.hidden) return;
58 createHintWindow(hintX, hintY);
63 // ////////////////////////////////////////////////////////////////////////// //
64 void createHintWindow (int x, int y) {
65 static GxRect getWorkArea () {
66 GxRect rc;
67 getWorkAreaRect(rc.x0, rc.y0, rc.width, rc.height);
68 return rc;
71 static GxSize hintWindowSize () {
72 laf.font = "ui";
73 laf.size = 18;
74 int textWidth = laf.textWidth(hinttext)+6;
75 if (textWidth < 8) textWidth = 8;
76 return GxSize(textWidth, laf.textHeight+4);
79 if (laf is null) return;
81 if (sdhint) {
82 sdhint.close();
83 sdhint = null;
86 NVGContext hintvg;
88 sdpyWindowClass = "BIOACID_HINT_WINDOW";
89 auto wsz = hintWindowSize();
91 auto wrc = getWorkArea();
92 int nx = x;
93 int ny = y;
94 if (nx+wsz.width > wrc.x1) nx = wrc.x1-wsz.width+1;
95 if (nx < wrc.x0) nx = wrc.x0;
96 if (ny+wsz.height > wrc.y1) ny = wrc.y1-wsz.height+1;
97 if (ny < wrc.y0) ny = wrc.y0;
98 hintX = nx;
99 hintY = ny;
102 if (sdhint is null || sdhint.closed) {
103 sdhint = new SimpleWindow(wsz.width, wsz.height, "BioAcidHint", OpenGlOptions.yes, Resizability.fixedSize, WindowTypes.undecorated, WindowFlags.skipTaskbar|WindowFlags.alwaysOnTop|WindowFlags.cannotBeActivated|WindowFlags.dontAutoShow);
104 XSetWindowBackground(sdhint.impl.display, sdhint.impl.window, gxRGB!(255, 255, 0));
107 sdhint.onClosing = delegate () {
108 if (hintvg !is null) {
109 sdhint.setAsCurrentOpenGlContext();
110 scope(exit) { flushGui(); sdhint.releaseCurrentOpenGlContext(); }
111 hintvg.kill();
112 assert(hintvg is null);
113 //conwriteln("ONCLOSING!");
117 sdhint.windowResized = delegate (int wdt, int hgt) {
118 if (sdhint.closed) return;
119 if (!sdhint.visible) return;
120 sdhint.redrawOpenGlSceneNow();
123 sdhint.visibleForTheFirstTime = delegate () {
124 sdhint.setAsCurrentOpenGlContext(); // make this window active
125 scope(exit) sdhint.releaseCurrentOpenGlContext();
126 sdhint.vsync = false;
127 hintvg = nvgCreateContext(NVGContextFlag.FontNoAA);
128 if (hintvg is null) assert(0, "cannot initialize NanoVG");
129 //hintvg.createFont("ui", uiFontNames[0]);
130 //if (nvg !is null) hintvg.addFontsFrom(nvg); else hintvg.createFont("ui", uiFontNames[0]);
131 hintvg.fonsContext.addFontsFrom(fstash);
132 //conwriteln("FIRST TIME");
133 //sdhint.redrawOpenGlSceneNow();
136 sdhint.redrawOpenGlScene = delegate () {
137 if (sdhint is null || sdhint.closed) return;
138 if (hintvg !is null) {
139 sdhint.setAsCurrentOpenGlContext(); // make this window active
140 scope(exit) sdhint.releaseCurrentOpenGlContext();
142 glClearColor(0, 0, 0, 0);
143 glClear(glNVGClearFlags/*|GL_COLOR_BUFFER_BIT*/);
145 hintvg.beginFrame(sdhint.width, sdhint.height);
146 scope(exit) hintvg.endFrame();
147 //hintvg.shapeAntiAlias = false;
149 //auto wsz = hintWindowSize();
150 //conwritefln!"sz=(%d,%d); wsz=(%d,%d); nsz=(%d,%d)"(sdhint.width, sdhint.height, wsz.width, wsz.height, hintvg.width, hintvg.height);
152 hintvg.newPath();
153 hintvg.rect(0.5, 0.5, hintvg.width-1, hintvg.height-1);
154 hintvg.strokeWidth = 1;
155 hintvg.strokeColor(NVGColor.black);
156 hintvg.fillColor(NVGColor.yellow);
157 hintvg.fill();
158 hintvg.stroke();
160 hintvg.fontFace = "ui";
161 hintvg.fontSize = 18;
162 hintvg.textAlign = NVGTextAlign.H.Left;
163 hintvg.textAlign = NVGTextAlign.V.Baseline;
164 hintvg.fillColor(NVGColor.black);
165 hintvg.text(3, 2+hintvg.textFontAscender, hinttext);
167 flushGui();
170 // sorry for this hack
171 sdhint.setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_DOCK", true)(sdhint.display));
172 //sdhint.setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_TOOLTIP", true)(sdhint.display));
174 Atom[4] atoms;
175 atoms[0] = GetAtom!("_NET_WM_STATE_STICKY", true)(sdhint.impl.display);
176 atoms[1] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(sdhint.impl.display);
177 atoms[2] = GetAtom!("_NET_WM_STATE_SKIP_PAGER", true)(sdhint.impl.display);
178 atoms[3] = GetAtom!("_NET_WM_STATE_ABOVE", true)(sdhint.impl.display);
179 XChangeProperty(
180 sdhint.impl.display,
181 sdhint.impl.window,
182 GetAtom!("_NET_WM_STATE", true)(sdhint.impl.display),
183 XA_ATOM,
184 32 /* bits */,
185 0 /*PropModeReplace*/,
186 atoms.ptr,
187 cast(int)atoms.length);
189 //if (sdhint.hidden) sdhint.show();
190 sdhint.show();
191 sdhint.moveResize(hintX, hintY, wsz.width, wsz.height);
192 //conwritefln!"CREATED! (%d,%d) [%s]"(wsz.width, wsz.height, hinttext);
193 flushGui();
197 // ////////////////////////////////////////////////////////////////////////// //
198 __gshared NotificationAreaIcon trayicon;
199 __gshared Image[6] trayimage; // offline, online, away; same as Status
200 __gshared ContactStatus traystatus = ContactStatus.Offline;
201 __gshared bool trayIsUnread = false;
202 __gshared bool trayBlinkStatus = true; // true: show kitty
203 __gshared Timer blinkTimer;
206 void setTrayUnread () {
207 if (!trayIsUnread) {
208 trayIsUnread = true;
209 if (blinkTimer is null) {
210 blinkTimer = new Timer(800, delegate () {
211 trayBlinkStatus = !trayBlinkStatus;
212 if (trayIsUnread) {
213 trayicon.icon = (trayBlinkStatus ? trayimage[$-1] : trayimage[traystatus]);
214 flushGui(); // or it may not redraw itself
218 if (trayBlinkStatus) {
219 trayicon.icon = trayimage[$-1];
220 flushGui(); // or it may not redraw itself
226 void setTrayStatus (ContactStatus status) {
227 if (status != traystatus || trayIsUnread) {
228 trayIsUnread = false;
229 traystatus = status;
230 trayicon.icon = trayimage[traystatus];
231 flushGui(); // or it may not redraw itself
236 void prepareTrayIcon () {
237 static Image buildTrayIcon (const(uint)[] img) {
238 auto icon = new TrueColorImage(16, 16);
239 scope(exit) { delete icon.imageData.bytes; delete icon; }
240 foreach (immutable y; 0..16) {
241 foreach (immutable x; 0..16) {
242 Color c;
243 c.asUint = img[y*16+x];
244 if (c.a < 0x20) c = Color.black;
245 else if (c.a < 0xff) {
246 c = c.toBW;
247 c = Color(c.r*c.a/255, c.g*c.a/255, c.b*c.a/255);
249 icon.setPixel(x, y, c);
252 return Image.fromMemoryImage(icon);
255 trayimage[ContactStatus.Offline] = buildTrayIcon(baph16Gray[]);
256 trayimage[ContactStatus.Online] = buildTrayIcon(baph16Online[]);
257 trayimage[ContactStatus.Away] = buildTrayIcon(baph16Away[]);
258 trayimage[ContactStatus.Busy] = buildTrayIcon(baph16Busy[]);
259 trayimage[ContactStatus.Connecting] = buildTrayIcon(baph16Orange[]);
260 trayimage[$-1] = buildTrayIcon(kittyMessage[]);
261 //traystatus = Contact.Status.Online;
262 trayicon = new NotificationAreaIcon("BioAcid", trayimage[traystatus], (int x, int y, MouseButton button, ModifierState mods) {
263 //conwritefln!"x=%d; y=%d; button=%u; mods=0x%04x"(x, y, button, mods);
264 if (button == MouseButton.middle) {
265 concmd("quit");
266 glconPostDoConCommands!true();
267 return;
269 if (button == MouseButton.left) {
270 concmd("win_toggle");
271 glconPostDoConCommands!true();
272 return;
275 trayicon.onEnter = delegate (int x, int y, ModifierState mods) {
276 if (sdhint is null || sdhint.hidden) {
277 createHintWindow(x+18, y+2);
278 if (hintHideTimer !is null) hintHideTimer.destroy();
279 if (sdhint !is null) {
280 hintHideTimer = new Timer(2000, delegate () {
281 if (hintHideTimer !is null) {
282 hintHideTimer.destroy();
283 hintHideTimer = null;
284 if (sdhint !is null && !sdhint.closed) {
285 sdhint.close();
286 sdhint = null;
287 //sdhint.hide();
288 flushGui();
290 sdhint = null; // just in case
296 trayicon.onLeave = delegate () {