X11Window.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Reactive.Disposables;
  7. using System.Text;
  8. using Avalonia.Controls;
  9. using Avalonia.Controls.Primitives.PopupPositioning;
  10. using Avalonia.Input;
  11. using Avalonia.Input.Raw;
  12. using Avalonia.OpenGL;
  13. using Avalonia.Platform;
  14. using Avalonia.Rendering;
  15. using Avalonia.Threading;
  16. using Avalonia.X11.Glx;
  17. using static Avalonia.X11.XLib;
  18. // ReSharper disable IdentifierTypo
  19. // ReSharper disable StringLiteralTypo
  20. namespace Avalonia.X11
  21. {
  22. unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client
  23. {
  24. private readonly AvaloniaX11Platform _platform;
  25. private readonly IWindowImpl _popupParent;
  26. private readonly bool _popup;
  27. private readonly X11Info _x11;
  28. private bool _invalidated;
  29. private XConfigureEvent? _configure;
  30. private PixelPoint? _configurePoint;
  31. private bool _triggeredExpose;
  32. private IInputRoot _inputRoot;
  33. private readonly IMouseDevice _mouse;
  34. private readonly IKeyboardDevice _keyboard;
  35. private PixelPoint? _position;
  36. private PixelSize _realSize;
  37. private IntPtr _handle;
  38. private IntPtr _xic;
  39. private IntPtr _renderHandle;
  40. private bool _mapped;
  41. private HashSet<X11Window> _transientChildren = new HashSet<X11Window>();
  42. private X11Window _transientParent;
  43. private double? _scalingOverride;
  44. public object SyncRoot { get; } = new object();
  45. class InputEventContainer
  46. {
  47. public RawInputEventArgs Event;
  48. }
  49. private readonly Queue<InputEventContainer> _inputQueue = new Queue<InputEventContainer>();
  50. private InputEventContainer _lastEvent;
  51. private bool _useRenderWindow = false;
  52. public X11Window(AvaloniaX11Platform platform, IWindowImpl popupParent)
  53. {
  54. _platform = platform;
  55. _popup = popupParent != null;
  56. _x11 = platform.Info;
  57. _mouse = platform.MouseDevice;
  58. _keyboard = platform.KeyboardDevice;
  59. var glfeature = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
  60. XSetWindowAttributes attr = new XSetWindowAttributes();
  61. var valueMask = default(SetWindowValuemask);
  62. attr.backing_store = 1;
  63. attr.bit_gravity = Gravity.NorthWestGravity;
  64. attr.win_gravity = Gravity.NorthWestGravity;
  65. valueMask |= SetWindowValuemask.BackPixel | SetWindowValuemask.BorderPixel
  66. | SetWindowValuemask.BackPixmap | SetWindowValuemask.BackingStore
  67. | SetWindowValuemask.BitGravity | SetWindowValuemask.WinGravity;
  68. if (_popup)
  69. {
  70. attr.override_redirect = true;
  71. valueMask |= SetWindowValuemask.OverrideRedirect;
  72. }
  73. XVisualInfo? visualInfo = null;
  74. // OpenGL seems to be do weird things to it's current window which breaks resize sometimes
  75. _useRenderWindow = glfeature != null;
  76. var glx = glfeature as GlxGlPlatformFeature;
  77. if (glx != null)
  78. visualInfo = *glx.Display.VisualInfo;
  79. else if (glfeature == null)
  80. visualInfo = _x11.TransparentVisualInfo;
  81. var egl = glfeature as EglGlPlatformFeature;
  82. var visual = IntPtr.Zero;
  83. var depth = 24;
  84. if (visualInfo != null)
  85. {
  86. visual = visualInfo.Value.visual;
  87. depth = (int)visualInfo.Value.depth;
  88. attr.colormap = XCreateColormap(_x11.Display, _x11.RootWindow, visualInfo.Value.visual, 0);
  89. valueMask |= SetWindowValuemask.ColorMap;
  90. }
  91. _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0,
  92. depth,
  93. (int)CreateWindowArgs.InputOutput,
  94. visual,
  95. new UIntPtr((uint)valueMask), ref attr);
  96. if (_useRenderWindow)
  97. _renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, depth,
  98. (int)CreateWindowArgs.InputOutput,
  99. visual,
  100. new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |
  101. SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr);
  102. else
  103. _renderHandle = _handle;
  104. Handle = new PlatformHandle(_handle, "XID");
  105. _realSize = new PixelSize(300, 200);
  106. platform.Windows[_handle] = OnEvent;
  107. XEventMask ignoredMask = XEventMask.SubstructureRedirectMask
  108. | XEventMask.ResizeRedirectMask
  109. | XEventMask.PointerMotionHintMask;
  110. if (platform.XI2 != null)
  111. ignoredMask |= platform.XI2.AddWindow(_handle, this);
  112. var mask = new IntPtr(0xffffff ^ (int)ignoredMask);
  113. XSelectInput(_x11.Display, _handle, mask);
  114. var protocols = new[]
  115. {
  116. _x11.Atoms.WM_DELETE_WINDOW
  117. };
  118. XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length);
  119. XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM,
  120. 32, PropertyMode.Replace, new[] {_x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL}, 1);
  121. if (platform.Options.WmClass != null)
  122. SetWmClass(platform.Options.WmClass);
  123. var surfaces = new List<object>
  124. {
  125. new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle,
  126. depth, () => Scaling)
  127. };
  128. if (egl != null)
  129. surfaces.Insert(0,
  130. new EglGlPlatformSurface((EglDisplay)egl.Display, egl.DeferredContext,
  131. new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle)));
  132. if (glx != null)
  133. surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext,
  134. new SurfaceInfo(this, _x11.Display, _handle, _renderHandle)));
  135. Surfaces = surfaces.ToArray();
  136. UpdateMotifHints();
  137. _xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
  138. XNames.XNClientWindow, _handle, IntPtr.Zero);
  139. XFlush(_x11.Display);
  140. if(_popup)
  141. PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize));
  142. }
  143. class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
  144. {
  145. private readonly X11Window _window;
  146. private readonly IntPtr _display;
  147. private readonly IntPtr _parent;
  148. public SurfaceInfo(X11Window window, IntPtr display, IntPtr parent, IntPtr xid)
  149. {
  150. _window = window;
  151. _display = display;
  152. _parent = parent;
  153. Handle = xid;
  154. }
  155. public IntPtr Handle { get; }
  156. public PixelSize Size
  157. {
  158. get
  159. {
  160. XLockDisplay(_display);
  161. XGetGeometry(_display, _parent, out var geo);
  162. XResizeWindow(_display, Handle, geo.width, geo.height);
  163. XUnlockDisplay(_display);
  164. return new PixelSize(geo.width, geo.height);
  165. }
  166. }
  167. public double Scaling => _window.Scaling;
  168. }
  169. void UpdateMotifHints()
  170. {
  171. var functions = MotifFunctions.Move | MotifFunctions.Close | MotifFunctions.Resize |
  172. MotifFunctions.Minimize | MotifFunctions.Maximize;
  173. var decorations = MotifDecorations.Menu | MotifDecorations.Title | MotifDecorations.Border |
  174. MotifDecorations.Maximize | MotifDecorations.Minimize | MotifDecorations.ResizeH;
  175. if (_popup || !_systemDecorations)
  176. {
  177. decorations = 0;
  178. }
  179. if (!_canResize)
  180. {
  181. functions &= ~(MotifFunctions.Resize | MotifFunctions.Maximize);
  182. decorations &= ~(MotifDecorations.Maximize | MotifDecorations.ResizeH);
  183. }
  184. var hints = new MotifWmHints
  185. {
  186. flags = new IntPtr((int)(MotifFlags.Decorations | MotifFlags.Functions)),
  187. decorations = new IntPtr((int)decorations),
  188. functions = new IntPtr((int)functions)
  189. };
  190. XChangeProperty(_x11.Display, _handle,
  191. _x11.Atoms._MOTIF_WM_HINTS, _x11.Atoms._MOTIF_WM_HINTS, 32,
  192. PropertyMode.Replace, ref hints, 5);
  193. }
  194. void UpdateSizeHints(PixelSize? preResize)
  195. {
  196. var min = _minMaxSize.minSize;
  197. var max = _minMaxSize.maxSize;
  198. if (!_canResize)
  199. max = min = _realSize;
  200. if (preResize.HasValue)
  201. {
  202. var desired = preResize.Value;
  203. max = new PixelSize(Math.Max(desired.Width, max.Width), Math.Max(desired.Height, max.Height));
  204. min = new PixelSize(Math.Min(desired.Width, min.Width), Math.Min(desired.Height, min.Height));
  205. }
  206. var hints = new XSizeHints
  207. {
  208. min_width = min.Width,
  209. min_height = min.Height
  210. };
  211. hints.height_inc = hints.width_inc = 1;
  212. var flags = XSizeHintsFlags.PMinSize | XSizeHintsFlags.PResizeInc;
  213. // People might be passing double.MaxValue
  214. if (max.Width < 100000 && max.Height < 100000)
  215. {
  216. hints.max_width = max.Width;
  217. hints.max_height = max.Height;
  218. flags |= XSizeHintsFlags.PMaxSize;
  219. }
  220. hints.flags = (IntPtr)flags;
  221. XSetWMNormalHints(_x11.Display, _handle, ref hints);
  222. }
  223. public Size ClientSize => new Size(_realSize.Width / Scaling, _realSize.Height / Scaling);
  224. public double Scaling
  225. {
  226. get
  227. {
  228. lock (SyncRoot)
  229. return _scaling;
  230. }
  231. private set => _scaling = value;
  232. }
  233. public IEnumerable<object> Surfaces { get; }
  234. public Action<RawInputEventArgs> Input { get; set; }
  235. public Action<Rect> Paint { get; set; }
  236. public Action<Size> Resized { get; set; }
  237. //TODO
  238. public Action<double> ScalingChanged { get; set; }
  239. public Action Deactivated { get; set; }
  240. public Action Activated { get; set; }
  241. public Func<bool> Closing { get; set; }
  242. public Action<WindowState> WindowStateChanged { get; set; }
  243. public Action Closed { get; set; }
  244. public Action<PixelPoint> PositionChanged { get; set; }
  245. public IRenderer CreateRenderer(IRenderRoot root) =>
  246. new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>());
  247. void OnEvent(XEvent ev)
  248. {
  249. lock (SyncRoot)
  250. OnEventSync(ev);
  251. }
  252. void OnEventSync(XEvent ev)
  253. {
  254. if(XFilterEvent(ref ev, _handle))
  255. return;
  256. if (ev.type == XEventName.MapNotify)
  257. {
  258. _mapped = true;
  259. if (_useRenderWindow)
  260. XMapWindow(_x11.Display, _renderHandle);
  261. }
  262. else if (ev.type == XEventName.UnmapNotify)
  263. _mapped = false;
  264. else if (ev.type == XEventName.Expose)
  265. {
  266. if (!_triggeredExpose)
  267. {
  268. _triggeredExpose = true;
  269. Dispatcher.UIThread.Post(() =>
  270. {
  271. _triggeredExpose = false;
  272. DoPaint();
  273. }, DispatcherPriority.Render);
  274. }
  275. }
  276. else if (ev.type == XEventName.FocusIn)
  277. {
  278. if (ActivateTransientChildIfNeeded())
  279. return;
  280. Activated?.Invoke();
  281. }
  282. else if (ev.type == XEventName.FocusOut)
  283. Deactivated?.Invoke();
  284. else if (ev.type == XEventName.MotionNotify)
  285. MouseEvent(RawPointerEventType.Move, ref ev, ev.MotionEvent.state);
  286. else if (ev.type == XEventName.LeaveNotify)
  287. MouseEvent(RawPointerEventType.LeaveWindow, ref ev, ev.CrossingEvent.state);
  288. else if (ev.type == XEventName.PropertyNotify)
  289. {
  290. OnPropertyChange(ev.PropertyEvent.atom, ev.PropertyEvent.state == 0);
  291. }
  292. else if (ev.type == XEventName.ButtonPress)
  293. {
  294. if (ActivateTransientChildIfNeeded())
  295. return;
  296. if (ev.ButtonEvent.button < 4)
  297. MouseEvent(ev.ButtonEvent.button == 1 ? RawPointerEventType.LeftButtonDown
  298. : ev.ButtonEvent.button == 2 ? RawPointerEventType.MiddleButtonDown
  299. : RawPointerEventType.RightButtonDown, ref ev, ev.ButtonEvent.state);
  300. else
  301. {
  302. var delta = ev.ButtonEvent.button == 4
  303. ? new Vector(0, 1)
  304. : ev.ButtonEvent.button == 5
  305. ? new Vector(0, -1)
  306. : ev.ButtonEvent.button == 6
  307. ? new Vector(1, 0)
  308. : new Vector(-1, 0);
  309. ScheduleInput(new RawMouseWheelEventArgs(_mouse, (ulong)ev.ButtonEvent.time.ToInt64(),
  310. _inputRoot, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), delta,
  311. TranslateModifiers(ev.ButtonEvent.state)), ref ev);
  312. }
  313. }
  314. else if (ev.type == XEventName.ButtonRelease)
  315. {
  316. if (ev.ButtonEvent.button < 4)
  317. MouseEvent(ev.ButtonEvent.button == 1 ? RawPointerEventType.LeftButtonUp
  318. : ev.ButtonEvent.button == 2 ? RawPointerEventType.MiddleButtonUp
  319. : RawPointerEventType.RightButtonUp, ref ev, ev.ButtonEvent.state);
  320. }
  321. else if (ev.type == XEventName.ConfigureNotify)
  322. {
  323. if (ev.ConfigureEvent.window != _handle)
  324. return;
  325. var needEnqueue = (_configure == null);
  326. _configure = ev.ConfigureEvent;
  327. if (ev.ConfigureEvent.override_redirect || ev.ConfigureEvent.send_event)
  328. _configurePoint = new PixelPoint(ev.ConfigureEvent.x, ev.ConfigureEvent.y);
  329. else
  330. {
  331. XTranslateCoordinates(_x11.Display, _handle, _x11.RootWindow,
  332. 0, 0,
  333. out var tx, out var ty, out _);
  334. _configurePoint = new PixelPoint(tx, ty);
  335. }
  336. if (needEnqueue)
  337. Dispatcher.UIThread.Post(() =>
  338. {
  339. if (_configure == null)
  340. return;
  341. var cev = _configure.Value;
  342. var npos = _configurePoint.Value;
  343. _configure = null;
  344. _configurePoint = null;
  345. var nsize = new PixelSize(cev.width, cev.height);
  346. var changedSize = _realSize != nsize;
  347. var changedPos = _position == null || npos != _position;
  348. _realSize = nsize;
  349. _position = npos;
  350. bool updatedSizeViaScaling = false;
  351. if (changedPos)
  352. {
  353. PositionChanged?.Invoke(npos);
  354. updatedSizeViaScaling = UpdateScaling();
  355. }
  356. if (changedSize && !updatedSizeViaScaling)
  357. Resized?.Invoke(ClientSize);
  358. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  359. }, DispatcherPriority.Layout);
  360. if (_useRenderWindow)
  361. XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width,
  362. ev.ConfigureEvent.height);
  363. }
  364. else if (ev.type == XEventName.DestroyNotify && ev.AnyEvent.window == _handle)
  365. {
  366. Cleanup();
  367. }
  368. else if (ev.type == XEventName.ClientMessage)
  369. {
  370. if (ev.ClientMessageEvent.message_type == _x11.Atoms.WM_PROTOCOLS)
  371. {
  372. if (ev.ClientMessageEvent.ptr1 == _x11.Atoms.WM_DELETE_WINDOW)
  373. {
  374. if (Closing?.Invoke() != true)
  375. Dispose();
  376. }
  377. }
  378. }
  379. else if (ev.type == XEventName.KeyPress || ev.type == XEventName.KeyRelease)
  380. {
  381. if (ActivateTransientChildIfNeeded())
  382. return;
  383. var buffer = stackalloc byte[40];
  384. var index = ev.KeyEvent.state.HasFlag(XModifierMask.ShiftMask);
  385. // We need the latin key, since it's mainly used for hotkeys, we use a different API for text anyway
  386. var key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 1 : 0).ToInt32();
  387. // Manually switch the Shift index for the keypad,
  388. // there should be a proper way to do this
  389. if (ev.KeyEvent.state.HasFlag(XModifierMask.Mod2Mask)
  390. && key > X11Key.Num_Lock && key <= X11Key.KP_9)
  391. key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();
  392. ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(),
  393. ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
  394. X11KeyTransform.ConvertKey(key), TranslateModifiers(ev.KeyEvent.state)), ref ev);
  395. if (ev.type == XEventName.KeyPress)
  396. {
  397. var len = Xutf8LookupString(_xic, ref ev, buffer, 40, out _, out _);
  398. if (len != 0)
  399. {
  400. var text = Encoding.UTF8.GetString(buffer, len);
  401. if (text.Length == 1)
  402. {
  403. if (text[0] < ' ' || text[0] == 0x7f) //Control codes or DEL
  404. return;
  405. }
  406. ScheduleInput(new RawTextInputEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), text),
  407. ref ev);
  408. }
  409. }
  410. }
  411. }
  412. private bool UpdateScaling(bool skipResize = false)
  413. {
  414. lock (SyncRoot)
  415. {
  416. double newScaling;
  417. if (_scalingOverride.HasValue)
  418. newScaling = _scalingOverride.Value;
  419. else
  420. {
  421. var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity)
  422. .FirstOrDefault(m => m.Bounds.Contains(Position));
  423. newScaling = monitor?.PixelDensity ?? Scaling;
  424. }
  425. if (Scaling != newScaling)
  426. {
  427. var oldScaledSize = ClientSize;
  428. Scaling = newScaling;
  429. ScalingChanged?.Invoke(Scaling);
  430. SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
  431. if(!skipResize)
  432. Resize(oldScaledSize, true);
  433. return true;
  434. }
  435. return false;
  436. }
  437. }
  438. private WindowState _lastWindowState;
  439. public WindowState WindowState
  440. {
  441. get => _lastWindowState;
  442. set
  443. {
  444. if(_lastWindowState == value)
  445. return;
  446. _lastWindowState = value;
  447. if (value == WindowState.Minimized)
  448. {
  449. XIconifyWindow(_x11.Display, _handle, _x11.DefaultScreen);
  450. }
  451. else if (value == WindowState.Maximized)
  452. {
  453. SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero);
  454. SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)1, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT,
  455. _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ);
  456. }
  457. else
  458. {
  459. SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero);
  460. SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT,
  461. _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ);
  462. }
  463. }
  464. }
  465. private void OnPropertyChange(IntPtr atom, bool hasValue)
  466. {
  467. if (atom == _x11.Atoms._NET_WM_STATE)
  468. {
  469. WindowState state = WindowState.Normal;
  470. if(hasValue)
  471. {
  472. XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_STATE, IntPtr.Zero, new IntPtr(256),
  473. false, (IntPtr)Atom.XA_ATOM, out _, out _, out var nitems, out _,
  474. out var prop);
  475. int maximized = 0;
  476. var pitems = (IntPtr*)prop.ToPointer();
  477. for (var c = 0; c < nitems.ToInt32(); c++)
  478. {
  479. if (pitems[c] == _x11.Atoms._NET_WM_STATE_HIDDEN)
  480. {
  481. state = WindowState.Minimized;
  482. break;
  483. }
  484. if (pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ ||
  485. pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT)
  486. {
  487. maximized++;
  488. if (maximized == 2)
  489. {
  490. state = WindowState.Maximized;
  491. break;
  492. }
  493. }
  494. }
  495. XFree(prop);
  496. }
  497. if (_lastWindowState != state)
  498. {
  499. _lastWindowState = state;
  500. WindowStateChanged?.Invoke(state);
  501. }
  502. }
  503. }
  504. InputModifiers TranslateModifiers(XModifierMask state)
  505. {
  506. var rv = default(InputModifiers);
  507. if (state.HasFlag(XModifierMask.Button1Mask))
  508. rv |= InputModifiers.LeftMouseButton;
  509. if (state.HasFlag(XModifierMask.Button2Mask))
  510. rv |= InputModifiers.RightMouseButton;
  511. if (state.HasFlag(XModifierMask.Button2Mask))
  512. rv |= InputModifiers.MiddleMouseButton;
  513. if (state.HasFlag(XModifierMask.ShiftMask))
  514. rv |= InputModifiers.Shift;
  515. if (state.HasFlag(XModifierMask.ControlMask))
  516. rv |= InputModifiers.Control;
  517. if (state.HasFlag(XModifierMask.Mod1Mask))
  518. rv |= InputModifiers.Alt;
  519. if (state.HasFlag(XModifierMask.Mod4Mask))
  520. rv |= InputModifiers.Windows;
  521. return rv;
  522. }
  523. private bool _systemDecorations = true;
  524. private bool _canResize = true;
  525. private const int MaxWindowDimension = 100000;
  526. private (Size minSize, Size maxSize) _scaledMinMaxSize =
  527. (new Size(1, 1), new Size(double.PositiveInfinity, double.PositiveInfinity));
  528. private (PixelSize minSize, PixelSize maxSize) _minMaxSize = (new PixelSize(1, 1),
  529. new PixelSize(MaxWindowDimension, MaxWindowDimension));
  530. private double _scaling = 1;
  531. void ScheduleInput(RawInputEventArgs args, ref XEvent xev)
  532. {
  533. _x11.LastActivityTimestamp = xev.ButtonEvent.time;
  534. ScheduleInput(args);
  535. }
  536. public void ScheduleInput(RawInputEventArgs args)
  537. {
  538. if (args is RawPointerEventArgs mouse)
  539. mouse.Position = mouse.Position / Scaling;
  540. if (args is RawDragEvent drag)
  541. drag.Location = drag.Location / Scaling;
  542. _lastEvent = new InputEventContainer() {Event = args};
  543. _inputQueue.Enqueue(_lastEvent);
  544. if (_inputQueue.Count == 1)
  545. {
  546. Dispatcher.UIThread.Post(() =>
  547. {
  548. while (_inputQueue.Count > 0)
  549. {
  550. Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
  551. var ev = _inputQueue.Dequeue();
  552. Input?.Invoke(ev.Event);
  553. }
  554. }, DispatcherPriority.Input);
  555. }
  556. }
  557. void MouseEvent(RawPointerEventType type, ref XEvent ev, XModifierMask mods)
  558. {
  559. var mev = new RawPointerEventArgs(
  560. _mouse, (ulong)ev.ButtonEvent.time.ToInt64(), _inputRoot,
  561. type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods));
  562. if(type == RawPointerEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawPointerEventArgs ma)
  563. if (ma.Type == RawPointerEventType.Move)
  564. {
  565. _lastEvent.Event = mev;
  566. return;
  567. }
  568. ScheduleInput(mev, ref ev);
  569. }
  570. void DoPaint()
  571. {
  572. _invalidated = false;
  573. Paint?.Invoke(new Rect());
  574. }
  575. public void Invalidate(Rect rect)
  576. {
  577. if(_invalidated)
  578. return;
  579. _invalidated = true;
  580. Dispatcher.UIThread.InvokeAsync(() =>
  581. {
  582. if (_mapped)
  583. DoPaint();
  584. });
  585. }
  586. public IInputRoot InputRoot => _inputRoot;
  587. public void SetInputRoot(IInputRoot inputRoot)
  588. {
  589. _inputRoot = inputRoot;
  590. }
  591. public void Dispose()
  592. {
  593. if (_handle != IntPtr.Zero)
  594. {
  595. XDestroyWindow(_x11.Display, _handle);
  596. Cleanup();
  597. }
  598. }
  599. void Cleanup()
  600. {
  601. SetTransientParent(null, false);
  602. if (_xic != IntPtr.Zero)
  603. {
  604. XDestroyIC(_xic);
  605. _xic = IntPtr.Zero;
  606. }
  607. if (_handle != IntPtr.Zero)
  608. {
  609. XDestroyWindow(_x11.Display, _handle);
  610. _platform.Windows.Remove(_handle);
  611. _platform.XI2?.OnWindowDestroyed(_handle);
  612. _handle = IntPtr.Zero;
  613. Closed?.Invoke();
  614. }
  615. if (_useRenderWindow && _renderHandle != IntPtr.Zero)
  616. {
  617. XDestroyWindow(_x11.Display, _renderHandle);
  618. _renderHandle = IntPtr.Zero;
  619. }
  620. }
  621. bool ActivateTransientChildIfNeeded()
  622. {
  623. if (_transientChildren.Count == 0)
  624. return false;
  625. var child = _transientChildren.First();
  626. if (!child.ActivateTransientChildIfNeeded())
  627. child.Activate();
  628. return true;
  629. }
  630. void SetTransientParent(X11Window window, bool informServer = true)
  631. {
  632. _transientParent?._transientChildren.Remove(this);
  633. _transientParent = window;
  634. _transientParent?._transientChildren.Add(this);
  635. if (informServer)
  636. XSetTransientForHint(_x11.Display, _handle, _transientParent?._handle ?? IntPtr.Zero);
  637. }
  638. public void Show()
  639. {
  640. SetTransientParent(null);
  641. ShowCore();
  642. }
  643. void ShowCore()
  644. {
  645. XMapWindow(_x11.Display, _handle);
  646. XFlush(_x11.Display);
  647. }
  648. public void Hide() => XUnmapWindow(_x11.Display, _handle);
  649. public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling);
  650. public PixelPoint PointToScreen(Point point) => new PixelPoint(
  651. (int)(point.X * Scaling + Position.X),
  652. (int)(point.Y * Scaling + Position.Y));
  653. public void SetSystemDecorations(bool enabled)
  654. {
  655. _systemDecorations = enabled;
  656. UpdateMotifHints();
  657. }
  658. public void Resize(Size clientSize) => Resize(clientSize, false);
  659. public void Move(PixelPoint point) => Position = point;
  660. private void MoveResize(PixelPoint position, Size size, double scaling)
  661. {
  662. Move(position);
  663. _scalingOverride = scaling;
  664. UpdateScaling(true);
  665. Resize(size, true);
  666. }
  667. PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * Scaling), (int)(size.Height * Scaling));
  668. void Resize(Size clientSize, bool force)
  669. {
  670. if (!force && clientSize == ClientSize)
  671. return;
  672. var needImmediatePopupResize = clientSize != ClientSize;
  673. var pixelSize = ToPixelSize(clientSize);
  674. UpdateSizeHints(pixelSize);
  675. XConfigureResizeWindow(_x11.Display, _handle, pixelSize);
  676. if (_useRenderWindow)
  677. XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize);
  678. XFlush(_x11.Display);
  679. if (force || (_popup && needImmediatePopupResize))
  680. {
  681. _realSize = pixelSize;
  682. Resized?.Invoke(ClientSize);
  683. }
  684. }
  685. public void CanResize(bool value)
  686. {
  687. _canResize = value;
  688. UpdateMotifHints();
  689. UpdateSizeHints(null);
  690. }
  691. public void SetCursor(IPlatformHandle cursor)
  692. {
  693. if (cursor == null)
  694. XDefineCursor(_x11.Display, _handle, _x11.DefaultCursor);
  695. else
  696. {
  697. if (cursor.HandleDescriptor != "XCURSOR")
  698. throw new ArgumentException("Expected XCURSOR handle type");
  699. XDefineCursor(_x11.Display, _handle, cursor.Handle);
  700. }
  701. }
  702. public IPlatformHandle Handle { get; }
  703. public PixelPoint Position
  704. {
  705. get => _position ?? default;
  706. set
  707. {
  708. var changes = new XWindowChanges
  709. {
  710. x = (int)value.X,
  711. y = (int)value.Y
  712. };
  713. XConfigureWindow(_x11.Display, _handle, ChangeWindowFlags.CWX | ChangeWindowFlags.CWY,
  714. ref changes);
  715. XFlush(_x11.Display);
  716. }
  717. }
  718. public IMouseDevice MouseDevice => _mouse;
  719. public IPopupImpl CreatePopup()
  720. => _platform.Options.OverlayPopups ? null : new X11Window(_platform, this);
  721. public void Activate()
  722. {
  723. if (_x11.Atoms._NET_ACTIVE_WINDOW != IntPtr.Zero)
  724. {
  725. SendNetWMMessage(_x11.Atoms._NET_ACTIVE_WINDOW, (IntPtr)1, _x11.LastActivityTimestamp,
  726. IntPtr.Zero);
  727. }
  728. else
  729. {
  730. XRaiseWindow(_x11.Display, _handle);
  731. XSetInputFocus(_x11.Display, _handle, 0, IntPtr.Zero);
  732. }
  733. }
  734. public IScreenImpl Screen => _platform.Screens;
  735. public Size MaxClientSize => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity))
  736. .OrderByDescending(x => x.Width + x.Height).FirstOrDefault();
  737. void SendNetWMMessage(IntPtr message_type, IntPtr l0,
  738. IntPtr? l1 = null, IntPtr? l2 = null, IntPtr? l3 = null, IntPtr? l4 = null)
  739. {
  740. var xev = new XEvent
  741. {
  742. ClientMessageEvent =
  743. {
  744. type = XEventName.ClientMessage,
  745. send_event = true,
  746. window = _handle,
  747. message_type = message_type,
  748. format = 32,
  749. ptr1 = l0,
  750. ptr2 = l1 ?? IntPtr.Zero,
  751. ptr3 = l2 ?? IntPtr.Zero,
  752. ptr4 = l3 ?? IntPtr.Zero
  753. }
  754. };
  755. xev.ClientMessageEvent.ptr4 = l4 ?? IntPtr.Zero;
  756. XSendEvent(_x11.Display, _x11.RootWindow, false,
  757. new IntPtr((int)(EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask)), ref xev);
  758. }
  759. void BeginMoveResize(NetWmMoveResize side)
  760. {
  761. var pos = GetCursorPos(_x11);
  762. XUngrabPointer(_x11.Display, new IntPtr(0));
  763. SendNetWMMessage (_x11.Atoms._NET_WM_MOVERESIZE, (IntPtr) pos.x, (IntPtr) pos.y,
  764. (IntPtr) side,
  765. (IntPtr) 1, (IntPtr)1); // left button
  766. }
  767. public void BeginMoveDrag()
  768. {
  769. BeginMoveResize(NetWmMoveResize._NET_WM_MOVERESIZE_MOVE);
  770. }
  771. public void BeginResizeDrag(WindowEdge edge)
  772. {
  773. var side = NetWmMoveResize._NET_WM_MOVERESIZE_CANCEL;
  774. if (edge == WindowEdge.East)
  775. side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_RIGHT;
  776. if (edge == WindowEdge.North)
  777. side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOP;
  778. if (edge == WindowEdge.South)
  779. side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOM;
  780. if (edge == WindowEdge.West)
  781. side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_LEFT;
  782. if (edge == WindowEdge.NorthEast)
  783. side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOPRIGHT;
  784. if (edge == WindowEdge.NorthWest)
  785. side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOPLEFT;
  786. if (edge == WindowEdge.SouthEast)
  787. side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;
  788. if (edge == WindowEdge.SouthWest)
  789. side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;
  790. BeginMoveResize(side);
  791. }
  792. public void SetTitle(string title)
  793. {
  794. var data = Encoding.UTF8.GetBytes(title);
  795. fixed (void* pdata = data)
  796. {
  797. XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME, _x11.Atoms.UTF8_STRING, 8,
  798. PropertyMode.Replace, pdata, data.Length);
  799. XStoreName(_x11.Display, _handle, title);
  800. }
  801. }
  802. public void SetWmClass(string wmClass)
  803. {
  804. var data = Encoding.ASCII.GetBytes(wmClass);
  805. fixed (void* pdata = data)
  806. {
  807. XChangeProperty(_x11.Display, _handle, _x11.Atoms.XA_WM_CLASS, _x11.Atoms.XA_STRING, 8,
  808. PropertyMode.Replace, pdata, data.Length);
  809. }
  810. }
  811. public void SetMinMaxSize(Size minSize, Size maxSize)
  812. {
  813. _scaledMinMaxSize = (minSize, maxSize);
  814. var min = new PixelSize(
  815. (int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling),
  816. (int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling));
  817. const int maxDim = MaxWindowDimension;
  818. var max = new PixelSize(
  819. (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * Scaling)),
  820. (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * Scaling)));
  821. _minMaxSize = (min, max);
  822. UpdateSizeHints(null);
  823. }
  824. public void SetTopmost(bool value)
  825. {
  826. SendNetWMMessage(_x11.Atoms._NET_WM_STATE,
  827. (IntPtr)(value ? 1 : 0), _x11.Atoms._NET_WM_STATE_ABOVE, IntPtr.Zero);
  828. }
  829. public void ShowDialog(IWindowImpl parent)
  830. {
  831. SetTransientParent((X11Window)parent);
  832. ShowCore();
  833. }
  834. public void SetIcon(IWindowIconImpl icon)
  835. {
  836. var data = ((X11IconData)icon).Data;
  837. fixed (void* pdata = data)
  838. XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_ICON,
  839. new IntPtr((int)Atom.XA_CARDINAL), 32, PropertyMode.Replace,
  840. pdata, data.Length);
  841. }
  842. public void ShowTaskbarIcon(bool value)
  843. {
  844. SendNetWMMessage(_x11.Atoms._NET_WM_STATE,
  845. (IntPtr)(value ? 0 : 1), _x11.Atoms._NET_WM_STATE_SKIP_TASKBAR, IntPtr.Zero);
  846. }
  847. public IPopupPositioner PopupPositioner { get; }
  848. }
  849. }