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
;
23 import arsd
.simpledisplay
;
30 import iv
.nanovega
.textlayouter
;
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))) {
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 () {
70 getWorkAreaRect(rc
.x0
, rc
.y0
, rc
.width
, rc
.height
);
74 static GxSize
hintWindowSize () {
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();
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
;
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(); }
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;
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
);
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
);
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));
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
);
173 GetAtom
!("_NET_WM_STATE", true)(sdhint
.impl
.display
),
176 0 /*PropModeReplace*/,
178 cast(int)atoms
.length
);
180 if (sdhint
.hidden
) sdhint
.show();
181 sdhint
.moveResize(hintX
, hintY
, wsz
.width
, wsz
.height
);
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 () {
198 if (blinkTimer
is null) {
199 blinkTimer
= new Timer(800, delegate () {
200 trayBlinkStatus
= !trayBlinkStatus
;
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;
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) {
232 c
.asUint
= img
[y
*16+x
];
233 if (c
.a
< 0x20) c
= Color
.black
;
234 else if (c
.a
< 0xff) {
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
) {
255 glconPostDoConCommands
!true();
258 if (button
== MouseButton
.left
) {
259 concmd("win_toggle");
260 glconPostDoConCommands
!true();
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
) {
284 trayicon
.onLeave
= delegate () {