1#include "taichi/ui/gui/gui.h"
2
3#if defined(TI_GUI_X11)
4#include <X11/Xlib.h>
5#include <X11/Xatom.h>
6#include <X11/Xutil.h>
7#include <cstdlib>
8
9// Undo terrible unprefixed macros in X.h
10#ifdef None
11#undef None
12#endif
13#ifdef Success
14#undef Success
15#endif
16
17namespace taichi {
18
19class CXImage {
20 public:
21 XImage *image;
22 std::vector<uint8> image_data;
23 void *fast_data{nullptr};
24 int width, height;
25
26 CXImage(Display *display, Visual *visual, int width, int height)
27 : width(width), height(height) {
28 image_data.resize(width * height * 4);
29 image = XCreateImage(display, visual, 24, ZPixmap, 0,
30 (char *)image_data.data(), width, height, 32, 0);
31 TI_ASSERT((void *)image->data == image_data.data());
32 }
33
34 CXImage(Display *display,
35 Visual *visual,
36 void *fast_data,
37 int width,
38 int height)
39 : width(width), height(height) {
40 image = XCreateImage(display, visual, 24, ZPixmap, 0, (char *)fast_data,
41 width, height, 32, 0);
42 TI_ASSERT((void *)image->data == fast_data);
43 }
44
45 void set_data(const Array2D<Vector4> &color) {
46 auto p = image_data.data();
47 for (int j = 0; j < height; j++) {
48 for (int i = 0; i < width; i++) {
49 auto c = color[i][height - j - 1];
50 *p++ = uint8(clamp(int(c[2] * 255.0_f), 0, 255));
51 *p++ = uint8(clamp(int(c[1] * 255.0_f), 0, 255));
52 *p++ = uint8(clamp(int(c[0] * 255.0_f), 0, 255));
53 *p++ = 0;
54 }
55 }
56 }
57
58 ~CXImage() {
59 delete image; // image->data is automatically released in image_data
60 }
61};
62
63static std::string lookup_keysym(XEvent *ev) {
64 int key = XLookupKeysym(&ev->xkey, 0);
65 if (isascii(key))
66 return std::string(1, key);
67 else
68 return XKeysymToString(key);
69}
70
71static std::string lookup_button(XEvent *ev) {
72 switch (ev->xbutton.button) {
73 case 1:
74 return "LMB";
75 case 2:
76 return "MMB";
77 case 3:
78 return "RMB";
79 default:
80 return fmt::format("Button{}", ev->xbutton.button);
81 }
82}
83
84void GUI::process_event() {
85 while (XPending((Display *)display)) {
86 XEvent ev;
87 XNextEvent((Display *)display, &ev);
88 switch (ev.type) {
89 case Expose:
90 break;
91 case ClientMessage:
92 // https://stackoverflow.com/questions/10792361/how-do-i-gracefully-exit-an-x11-event-loop
93 if (ev.xclient.data.l[0] == *(Atom *)wmDeleteMessage.data()) {
94 send_window_close_message();
95 }
96 break;
97 case MotionNotify:
98 set_mouse_pos(ev.xbutton.x, height - ev.xbutton.y - 1);
99 mouse_event(MouseEvent{MouseEvent::Type::move, cursor_pos});
100 key_events.push_back(
101 KeyEvent{KeyEvent::Type::move, "Motion", cursor_pos});
102 break;
103 case ButtonPress:
104 set_mouse_pos(ev.xbutton.x, height - ev.xbutton.y - 1);
105 mouse_event(MouseEvent{MouseEvent::Type::press, cursor_pos});
106 switch (ev.xbutton.button) {
107 case 4: // wheel up
108 key_events.push_back(KeyEvent{KeyEvent::Type::move, "Wheel",
109 cursor_pos, Vector2i{0, +120}});
110 break;
111 case 5: // wheel down
112 key_events.push_back(KeyEvent{KeyEvent::Type::move, "Wheel",
113 cursor_pos, Vector2i{0, -120}});
114 break;
115 case 6: // wheel right
116 key_events.push_back(KeyEvent{KeyEvent::Type::move, "Wheel",
117 cursor_pos, Vector2i{+120, 0}});
118 break;
119 case 7: // wheel left
120 key_events.push_back(KeyEvent{KeyEvent::Type::move, "Wheel",
121 cursor_pos, Vector2i{-120, 0}});
122 break;
123 default: // normal mouse button
124 key_events.push_back(KeyEvent{KeyEvent::Type::press,
125 lookup_button(&ev), cursor_pos});
126 break;
127 }
128 break;
129 case ButtonRelease:
130 set_mouse_pos(ev.xbutton.x, height - ev.xbutton.y - 1);
131 mouse_event(MouseEvent{MouseEvent::Type::release, cursor_pos});
132 key_events.push_back(
133 KeyEvent{KeyEvent::Type::release, lookup_button(&ev), cursor_pos});
134 break;
135 case KeyPress:
136 key_pressed = true;
137 key_events.push_back(
138 KeyEvent{KeyEvent::Type::press, lookup_keysym(&ev), cursor_pos});
139 break;
140 case KeyRelease:
141 key_events.push_back(
142 KeyEvent{KeyEvent::Type::release, lookup_keysym(&ev), cursor_pos});
143 break;
144 }
145 }
146}
147
148void GUI::create_window() {
149 display = XOpenDisplay(nullptr);
150 TI_ASSERT_INFO(display,
151 "Taichi fails to create a window."
152 " This is probably due to the lack of an X11 GUI environment."
153 " Consider using the `ti.GUI(show_gui=False)` option, see"
154 " https://docs.taichi-lang.org/docs/gui_system");
155 visual = DefaultVisual(display, 0);
156 window =
157 XCreateSimpleWindow((Display *)display, RootWindow((Display *)display, 0),
158 0, 0, width, height, 1, 0, 0);
159 TI_ASSERT_INFO(window, "failed to create X window");
160
161 if (fullscreen) {
162 // https://stackoverflow.com/questions/9083273/x11-fullscreen-window-opengl
163 Atom atoms[2] = {
164 XInternAtom((Display *)display, "_NET_WM_STATE_FULLSCREEN", False), 0};
165 Atom wmstate = XInternAtom((Display *)display, "_NET_WM_STATE", False);
166 XChangeProperty((Display *)display, window, wmstate, XA_ATOM, 32,
167 PropModeReplace, (unsigned char *)atoms, 1);
168 }
169
170 XSelectInput((Display *)display, window,
171 ButtonPressMask | ExposureMask | KeyPressMask | KeyReleaseMask |
172 ButtonPress | ButtonReleaseMask | EnterWindowMask |
173 LeaveWindowMask | PointerMotionMask);
174 wmDeleteMessage = std::vector<char>(sizeof(Atom));
175 *(Atom *)wmDeleteMessage.data() =
176 XInternAtom((Display *)display, "WM_DELETE_WINDOW", False);
177 XSetWMProtocols((Display *)display, window, (Atom *)wmDeleteMessage.data(),
178 1);
179 XMapWindow((Display *)display, window);
180 if (!fast_gui)
181 img = new CXImage((Display *)display, (Visual *)visual, width, height);
182 else
183 img = new CXImage((Display *)display, (Visual *)visual, (void *)fast_buf,
184 width, height);
185}
186
187void GUI::redraw() {
188 if (!fast_gui)
189 img->set_data(buffer);
190 XPutImage((Display *)display, window, DefaultGC(display, 0), img->image, 0, 0,
191 0, 0, width, height);
192}
193
194void GUI::set_title(std::string title) {
195 XStoreName((Display *)display, window, title.c_str());
196}
197
198GUI::~GUI() {
199 if (show_gui) {
200 XCloseDisplay((Display *)display);
201 delete img;
202 }
203}
204
205} // namespace taichi
206
207#endif
208