├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── LICENSE
├── README.md
├── example.c
├── makefile
├── messagebox.c
├── messagebox.h
└── screenshot.png
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - name: Build
11 | run: make
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | example
2 | example.o
3 | messagebox.o
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 Eleobert Espírito Santo eleobert@hotmail.com
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a lightweight message box project, using only X11 for rendering, which is very useful if you want to avoid external dependencies on your projects. The function is designed to give you flexibility. And you can easily customize the output by changing the source code.
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/example.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 | #include "messagebox.h"
5 |
6 | int main(){
7 | Button buttons[3];
8 | //language
9 | wchar_t lang_bt_eng[] = L"English";
10 | wchar_t lang_bt_rus[] = L"Русский";
11 | wchar_t lang_bt_arb[] = L"عربى";
12 |
13 | buttons[0].label = lang_bt_eng;
14 | buttons[1].label = lang_bt_rus;
15 | buttons[2].label = lang_bt_arb;
16 |
17 | buttons[0].result = 1;
18 | buttons[1].result = 2;
19 | buttons[2].result = 3;
20 |
21 | int res;
22 |
23 | res = Messagebox("Language - Язык - لغة",
24 | L"Please choose a language.\nПожалуйста, выберите язык.\nمن فضلك ، اختر لغة.", buttons, 3);
25 |
26 | Button yn_bts;
27 |
28 | if(res == 1){
29 | buttons[0].label = L"No";
30 | buttons[1].label = L"Yes";
31 | res = Messagebox("Answer this question",
32 | L"Do you like to program in C language?", buttons, 2);
33 | if(res == 1){
34 | buttons[0].label = L"Accept";
35 | res = Messagebox("Oops",
36 | L"Unfortunately, you are a bad person.\nThere is nothing I can do for you.", buttons, 1);
37 | }
38 | }
39 | else if(res == 2){
40 | buttons[0].label = L"Нет";
41 | buttons[1].label = L"Да";
42 | res = Messagebox("Ответьте на этот вопрос",
43 | L"Вы любите программировать на языке C?", buttons, 2);
44 | if(res == 1){
45 | buttons[0].label = L"Принимать";
46 | res = Messagebox("Oops",
47 | L"К сожалению, вы плохой человек.\nНичего не могу для вас сделать.", buttons, 1);
48 | }
49 | }
50 | else if(res == 3){
51 | buttons[0].label = L"لا";
52 | buttons[1].label = L"نعم";
53 | res = Messagebox("أجب على هذا السؤال",
54 | L"هل تحب البرمجة بلغة C؟", buttons, 2);
55 | if(res == 1){
56 | buttons[0].label = L"قبول";
57 | res = Messagebox("Oops",
58 | L"لسوء الحظ ، أنت شخص سيئ.\n"
59 | "لا يوجد شيء يمكنني القيام به من أجلك.", buttons, 1);
60 | }
61 | }
62 |
63 |
64 |
65 |
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | # for GNU make
2 |
3 | TARGET = example
4 | SRCS = messagebox.c example.c
5 | OBJS = $(SRCS:.c=.o)
6 | CFLAGS = -Wall
7 |
8 | PKG_CONFIG ?= $(shell which pkg-config)
9 | ifeq ($(PKG_CONFIG),)
10 | LDLIBS += -lX11
11 | else
12 | CFLAGS += $(shell $(PKG_CONFIG) --cflags x11)
13 | LDFLAGS += $(shell $(PKG_CONFIG) --libs-only-L x11)
14 | LDLIBS += $(shell $(PKG_CONFIG) --libs-only-l x11)
15 | endif
16 |
17 | default: $(TARGET)
18 |
19 | all: default
20 |
21 | build: default
22 |
23 | $(TARGET): $(OBJS)
24 |
25 | run: $(TARGET)
26 | ./$(TARGET)
27 |
28 | clean:
29 | $(RM) $(TARGET) $(OBJS)
30 |
31 | .PHONY: default all clean run
32 |
--------------------------------------------------------------------------------
/messagebox.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 Eleobert Espírito Santo eleobert@hotmail.com
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 |
23 |
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include "messagebox.h"
34 |
35 |
36 | struct Dimensions{
37 | //window
38 | unsigned int winMinWidth;
39 | unsigned int winMinHeight;
40 | //vertical space between lines
41 | unsigned int lineSpacing;
42 | unsigned int barHeight;
43 | //padding
44 | unsigned int pad_up;
45 | unsigned int pad_down;
46 | unsigned int pad_left;
47 | unsigned int pad_right;
48 | //button
49 | unsigned int btSpacing;
50 | unsigned int btMinWidth;
51 | unsigned int btMinHeight;
52 | unsigned int btLateralPad;
53 |
54 | };
55 |
56 |
57 | typedef struct ButtonData{ //<----see static
58 | const Button* button;
59 | GC *gc;
60 | XRectangle rect;
61 | }ButtonData;
62 |
63 | //these values can be changed to whatever you prefer
64 | struct Dimensions dim = {400, 0, 5, 50, 30, 10, 30, 30, 20, 75, 25, 8};
65 |
66 |
67 | void setWindowTitle(const char* title, const Window *win, Display *dpy){
68 | Atom wm_Name = XInternAtom(dpy, "_NET_WM_NAME", False);
69 | Atom utf8Str = XInternAtom(dpy, "UTF8_STRING", False);
70 |
71 | Atom winType = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False );
72 | Atom typeDialog = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False );
73 |
74 | XChangeProperty(dpy, *win, wm_Name, utf8Str, 8, PropModeReplace, (const unsigned char*)title, (int)strlen(title));
75 |
76 | XChangeProperty( dpy,*win, winType, XA_ATOM,
77 | 32, PropModeReplace, (unsigned char *)&typeDialog,
78 | 1);
79 | }
80 |
81 | void split(const wchar_t* text, const wchar_t* seps, wchar_t ***str, int *count){
82 | wchar_t *last, *tok, *data;
83 | int i;
84 | *count = 0;
85 | data = wcsdup(text);
86 |
87 | for (tok = wcstok(data, seps, &last); tok != NULL; tok = wcstok(NULL, seps, &last))
88 | (*count)++;
89 |
90 | free(data);
91 | fflush(stdout);
92 | data = wcsdup(text);
93 | *str = (wchar_t **)malloc((size_t)(*count)*sizeof(wchar_t*));
94 |
95 |
96 | for (i = 0, tok = wcstok(data, seps, &last); tok != NULL; tok = wcstok(NULL, seps, &last), i++)
97 | (*str)[i] = wcsdup(tok);
98 | free(data);
99 | }
100 |
101 | void computeTextSize(XFontSet* fs, wchar_t** texts, int size, unsigned int spaceBetweenLines,
102 | unsigned int *w, unsigned int *h)
103 | {
104 | int i;
105 | XRectangle rect = {0,0,0,0};
106 | *h = 0;
107 | *w = 0;
108 | for(i = 0; i < size; i++){
109 | XwcTextExtents(*fs, texts[i], (int)wcslen(texts[i]), &rect, NULL);
110 | *w = (rect.width > *w) ? (rect.width): *w;
111 | *h += rect.height + spaceBetweenLines;
112 | fflush(stdin);
113 | }
114 | }
115 |
116 | void createGC(GC* gc, const Colormap* cmap, Display* dpy, const Window* win,
117 | unsigned char red, unsigned char green, unsigned char blue){
118 | float coloratio = (float) 65535 / 255;
119 | XColor color;
120 | *gc = XCreateGC (dpy, *win, 0,0);
121 | memset(&color, 0, sizeof(color));
122 | color.red = (unsigned short)(coloratio * red );
123 | color.green = (unsigned short)(coloratio * green);
124 | color.blue = (unsigned short)(coloratio * blue );
125 | color.flags = DoRed | DoGreen | DoBlue;
126 | XAllocColor (dpy, *cmap, &color);
127 | XSetForeground (dpy, *gc, color.pixel);
128 | }
129 |
130 | bool isInside(int x, int y, XRectangle rect){
131 | if(x < rect.x || x > (rect.x + rect.width) || y < rect.y || y > (rect.y + rect.height))
132 | return false;
133 | return true;
134 | }
135 |
136 | int Messagebox(const char* title, const wchar_t* text, const Button* buttons, int numButtons)
137 | {
138 | setlocale(LC_ALL,"");
139 |
140 | //---- convert the text in list (to draw in multiply lines)---------------------------------------------------------
141 | wchar_t** text_splitted = NULL;
142 | int textLines = 0;
143 | split(text, L"\n" , &text_splitted, &textLines);
144 | //------------------------------------------------------------------------------------------------------------------
145 |
146 | Display* dpy = NULL;
147 | dpy = XOpenDisplay(0);
148 | if(dpy == NULL){
149 | fprintf(stderr, "Error opening display display.");
150 | }
151 |
152 | int ds = DefaultScreen(dpy);
153 | Window win = XCreateSimpleWindow(dpy, RootWindow(dpy, ds), 0, 0, 800, 100, 1,
154 | BlackPixel(dpy, ds), WhitePixel(dpy, ds));
155 |
156 |
157 | XSelectInput(dpy, win, ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
158 | XMapWindow(dpy, win);
159 |
160 | //allow windows to be closed by pressing cross button (but it wont close - see ClientMessage on switch)
161 | Atom WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
162 | XSetWMProtocols(dpy, win, &WM_DELETE_WINDOW, 1);
163 |
164 | //--create the gc for drawing text----------------------------------------------------------------------------------
165 | XGCValues gcValues;
166 | gcValues.font = XLoadFont(dpy, "7x13");
167 | gcValues.foreground = BlackPixel(dpy, 0);
168 | GC textGC = XCreateGC(dpy, win, GCFont + GCForeground, &gcValues);
169 | XUnmapWindow( dpy, win );
170 | //------------------------------------------------------------------------------------------------------------------
171 |
172 | //----create fontset-----------------------------------------------------------------------------------------------
173 | char **missingCharset_list = NULL;
174 | int missingCharset_count = 0;
175 | XFontSet fs;
176 | fs = XCreateFontSet(dpy,
177 | "-*-*-medium-r-*-*-*-140-75-75-*-*-*-*" ,
178 | &missingCharset_list, &missingCharset_count, NULL);
179 |
180 | if (missingCharset_count) {
181 | fprintf(stderr, "Missing charsets :\n");
182 | for(int i = 0; i < missingCharset_count; i++){
183 | fprintf(stderr, "%s\n", missingCharset_list[i]);
184 | }
185 | XFreeStringList(missingCharset_list);
186 | missingCharset_list = NULL;
187 | }
188 | //------------------------------------------------------------------------------------------------------------------
189 |
190 | Colormap cmap = DefaultColormap (dpy, ds);
191 |
192 | //resize the window according to the text size-----------------------------------------------------------------------
193 | unsigned int winW, winH;
194 | unsigned int textW, textH;
195 |
196 | //calculate the ideal window's size
197 | computeTextSize(&fs, text_splitted, textLines, dim.lineSpacing, &textW, &textH);
198 | unsigned int newWidth = textW + dim.pad_left + dim.pad_right;
199 | unsigned int newHeight = textH + dim.pad_up + dim.pad_down + dim.barHeight;
200 | winW = (newWidth > dim.winMinWidth)? newWidth: dim.winMinWidth;
201 | winH = (newHeight > dim.winMinHeight)? newHeight: dim.winMinHeight;
202 |
203 | //set windows hints
204 | XSizeHints hints;
205 | hints.flags = PSize | PMinSize | PMaxSize;
206 | hints.min_width = hints.max_width = hints.base_width = winW;
207 | hints.min_height = hints.max_height = hints.base_height = winH;
208 |
209 | XSetWMNormalHints( dpy, win, &hints );
210 | XMapRaised( dpy, win );
211 | //------------------------------------------------------------------------------------------------------------------
212 |
213 | GC barGC;
214 | GC buttonGC;
215 | GC buttonGC_underPointer;
216 | GC buttonGC_onClick; // GC colors
217 | createGC(&barGC, &cmap, dpy, &win, 242, 242, 242);
218 | createGC(&buttonGC, &cmap, dpy, &win, 202, 202, 202);
219 | createGC(&buttonGC_underPointer, &cmap, dpy, &win, 192, 192, 192);
220 | createGC(&buttonGC_onClick, &cmap, dpy, &win, 182, 182, 182);
221 |
222 | //---setup the buttons data-----------------------------------------------------------------------------------------
223 | ButtonData *btsData;
224 | btsData = (ButtonData*)malloc((size_t)numButtons * sizeof(ButtonData));
225 |
226 | int pass = 0;
227 | for(int i = 0; i < numButtons; i++){
228 | btsData[i].button = &buttons[i];
229 | btsData[i].gc = &buttonGC;
230 | XRectangle btTextDim;
231 | XwcTextExtents(fs, btsData[i].button->label, (int)wcslen(btsData[i].button->label),
232 | &btTextDim, NULL);
233 | btsData[i].rect.width = (btTextDim.width < dim.btMinWidth) ? dim.btMinWidth:
234 | (btTextDim.width + 2 * dim.btLateralPad);
235 | btsData[i].rect.height = dim.btMinHeight;
236 | btsData[i].rect.x = winW - dim.pad_left - btsData[i].rect.width - pass;
237 | btsData[i].rect.y = textH + dim.pad_up + dim.pad_down + ((dim.barHeight - dim.btMinHeight)/ 2) ;
238 | pass += btsData[i].rect.width + dim.btSpacing;
239 | }
240 | //------------------------------------------------------------------------------------------------------------------
241 |
242 | setWindowTitle(title, &win, dpy);
243 |
244 | XFlush( dpy );
245 |
246 |
247 | bool quit = false;
248 | int res = -1;
249 |
250 | while( !quit ) {
251 | XEvent e;
252 | XNextEvent( dpy, &e );
253 |
254 | switch (e.type){
255 |
256 | case MotionNotify:
257 | case ButtonPress:
258 | case ButtonRelease:
259 | for(int i = 0; i < numButtons; i++) {
260 | btsData[i].gc = &buttonGC;
261 | if (isInside(e.xmotion.x, e.xmotion.y, btsData[i].rect)) {
262 | btsData[i].gc = &buttonGC_underPointer;
263 | if(e.type == ButtonPress && e.xbutton.button == Button1) {
264 | btsData[i].gc = &buttonGC_onClick;
265 | res = btsData[i].button->result;
266 | quit = true;
267 | }
268 | }
269 | }
270 |
271 | case Expose:
272 | //draw the text in multiply lines----------------------------------------------------------------------
273 | for(int i = 0; i < textLines; i++){
274 |
275 | XwcDrawString( dpy, win, fs, textGC, dim.pad_left, dim.pad_up + i * (dim.lineSpacing + 18),
276 | text_splitted[i], (int)wcslen(text_splitted[i]));
277 | }
278 | //------------------------------------------------------------------------------------------------------
279 | XFillRectangle(dpy, win, barGC, 0, textH + dim.pad_up + dim.pad_down, winW, dim.barHeight);
280 |
281 | for(int i = 0; i < numButtons; i++){
282 | XFillRectangle(dpy, win, *btsData[i].gc, btsData[i].rect.x, btsData[i].rect.y,
283 | btsData[i].rect.width, btsData[i].rect.height);
284 |
285 | XRectangle btTextDim;
286 | XwcTextExtents(fs, btsData[i].button->label, (int)wcslen(btsData[i].button->label),
287 | &btTextDim, NULL);
288 | XwcDrawString( dpy, win, fs, textGC,
289 | btsData[i].rect.x + (btsData[i].rect.width - btTextDim.width ) / 2,
290 | btsData[i].rect.y + (btsData[i].rect.height + btTextDim.height) / 2,
291 | btsData[i].button->label, (int)wcslen(btsData[i].button->label));
292 | }
293 | XFlush(dpy);
294 |
295 | break;
296 |
297 | case ClientMessage:
298 | //if window's cross button pressed does nothing
299 | //if((unsigned int)(e.xclient.data.l[0]) == WM_DELETE_WINDOW)
300 | //quit = true;
301 | break;
302 | default:
303 | break;
304 | }
305 | }
306 |
307 | for(int i = 0; i < textLines; i++){
308 | free(text_splitted[i]);
309 | }
310 | free(text_splitted);
311 | free(btsData);
312 | if (missingCharset_list)
313 | XFreeStringList(missingCharset_list);
314 | XDestroyWindow(dpy, win);
315 | XFreeFontSet(dpy, fs);
316 | XFreeGC(dpy, textGC);
317 | XFreeGC(dpy, barGC);
318 | XFreeGC(dpy, buttonGC);
319 | XFreeGC(dpy, buttonGC_underPointer);
320 | XFreeGC(dpy, buttonGC_onClick);
321 | XFreeColormap(dpy, cmap);
322 | XCloseDisplay(dpy);
323 |
324 | return res;
325 | }
326 |
--------------------------------------------------------------------------------
/messagebox.h:
--------------------------------------------------------------------------------
1 | #ifndef MESSAGEBOX_X11_MESSAGEBOX_H
2 | #define MESSAGEBOX_X11_MESSAGEBOX_H
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif
7 |
8 | #include
9 |
10 | typedef struct Button{
11 | wchar_t *label;
12 | int result;
13 | }Button;
14 |
15 | int Messagebox(const char* title, const wchar_t* text, const Button* buttons, int numButtons);
16 |
17 | #ifdef __cplusplus
18 | }
19 | #endif
20 |
21 | #endif //MESSAGEBOX_X11_MESSAGEBOX_H
22 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eleobert/MessageBox-X11/0fc63f4159282bb6c9d94a3bdcdbac209b576efd/screenshot.png
--------------------------------------------------------------------------------