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
;
22 import arsd
.simpledisplay
;
29 import iv
.nanovega
.textlayouter
;
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))) {
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 () {
67 getWorkAreaRect(rc
.x0
, rc
.y0
, rc
.width
, rc
.height
);
71 static GxSize
hintWindowSize () {
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;
88 sdpyWindowClass
= "BIOACID_HINT_WINDOW";
89 auto wsz
= hintWindowSize();
91 auto wrc
= getWorkArea();
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
;
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(); }
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);
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
);
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
);
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));
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
);
182 GetAtom
!("_NET_WM_STATE", true)(sdhint
.impl
.display
),
185 0 /*PropModeReplace*/,
187 cast(int)atoms
.length
);
189 //if (sdhint.hidden) sdhint.show();
191 sdhint
.moveResize(hintX
, hintY
, wsz
.width
, wsz
.height
);
192 //conwritefln!"CREATED! (%d,%d) [%s]"(wsz.width, wsz.height, hinttext);
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 () {
209 if (blinkTimer
is null) {
210 blinkTimer
= new Timer(800, delegate () {
211 trayBlinkStatus
= !trayBlinkStatus
;
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;
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) {
243 c
.asUint
= img
[y
*16+x
];
244 if (c
.a
< 0x20) c
= Color
.black
;
245 else if (c
.a
< 0xff) {
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
) {
266 glconPostDoConCommands
!true();
269 if (button
== MouseButton
.left
) {
270 concmd("win_toggle");
271 glconPostDoConCommands
!true();
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
) {
290 sdhint
= null; // just in case
296 trayicon
.onLeave
= delegate () {