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 | |
17 | namespace taichi { |
18 | |
19 | class 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 | |
63 | static 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 | |
71 | static 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 | |
84 | void 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 | |
148 | void 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 | |
187 | void 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 | |
194 | void GUI::set_title(std::string title) { |
195 | XStoreName((Display *)display, window, title.c_str()); |
196 | } |
197 | |
198 | GUI::~GUI() { |
199 | if (show_gui) { |
200 | XCloseDisplay((Display *)display); |
201 | delete img; |
202 | } |
203 | } |
204 | |
205 | } // namespace taichi |
206 | |
207 | #endif |
208 | |