pax_global_header00006660000000000000000000000064147213750660014525gustar00rootroot0000000000000052 comment=31e1c4194722ea5183c4075a2cab10f4360ded54 wstroke-2.2.1/000077500000000000000000000000001472137506600132255ustar00rootroot00000000000000wstroke-2.2.1/LICENSE000066400000000000000000000014651472137506600142400ustar00rootroot00000000000000Copyright (c) 2008-2009, Thomas Jaeger Copyright (c) 2020-2023, Daniel Kondor Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. wstroke-2.2.1/NEWS000066400000000000000000000021361472137506600137260ustar00rootroot000000000000002.2.1 ========= * Fix a build issue * Build now requires meson version >= 1.0.0 2.2.0 ========= * Support wlroots 0.18 (if used by Wayfire) * Add a "rotate cube" action 2.1.3 ========= * Add man page * Make rebuilding Vala code the default 2.1.2 ========= * Bug fix: avoid crashing if a gesture is started on a popup 2.1.1 ========= * WM Action / Move: avoid a spurious move of the view when the action is initiated 2.1 ========= * Command action: allow to select an app to run graphically 2.0.2 ========= * Avoid a potential edge case where refocusing the original view conflicts with the Command action. * Refactor the use of GtkApplication. 2.0.1 ========= * Allow recording strokes with the same button that is used by the plugin 2.0 ========= * Refactor internal storage and clean code * Gestures can be freely reordered * Fix issues related to the display of apps and groups * Slight updates to the GUI * Add "Touchpad Gesture" action (including the "Scroll" action from Easystroke) * Add the possibility to import / export the gesture configuration 1.0 ========= * Original release, most features work wstroke-2.2.1/README.md000066400000000000000000000213271472137506600145110ustar00rootroot00000000000000# wstroke Port of [Easystroke mouse gestures](https://github.com/thjaeger/easystroke) as a plugin for [Wayfire](https://github.com/WayfireWM/wayfire). Mouse gestures are shapes drawn on the screen while holding down one of the buttons (typically the right or middle button). This plugin allows associating such gestures with various actions. See the [Wiki](https://github.com/dkondor/wstroke/wiki) for more explanations and examples. Packages are available for: - Ubuntu 24.04: https://launchpad.net/~kondor-dani/+archive/ubuntu/ppa-wstroke - Debian (testing and unstable): in the official [repository](https://packages.debian.org/testing/wstroke). ### Dependencies - [Wayfire](https://github.com/WayfireWM/wayfire) version [0.8.0](https://github.com/WayfireWM/wayfire/tree/v0.8.0), [0.9.0](https://github.com/WayfireWM/wayfire/tree/v0.9.0) or the current development version (see below for compiling for older Wayfire versions) - [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) version [0.16](https://gitlab.freedesktop.org/wlroots/wlroots/-/tree/0.16?ref_type=heads), [0.17](https://gitlab.freedesktop.org/wlroots/wlroots/-/tree/0.17?ref_type=heads) or [0.18](https://gitlab.freedesktop.org/wlroots/wlroots/-/tree/0.18?ref_type=heads). - Development libraries for GTK, GDK, glib, gtkmm, gdkmm and boost-serialization (Ubuntu packages: `libglib2.0-dev, libgtk-3-dev, libgtkmm-3.0-dev, libboost-serialization-dev`) - `glib-compile-resources` (Ubuntu package: `libglib2.0-dev-bin`) - [nlohmann_json](https://github.com/nlohmann/json/), recommended to use the same version that Wayfire uses (currently version 3.9.1) - [Vala](https://vala.dev/) compiler (for building, Ubuntu package: `valac`; or use the [no_vala](https://github.com/dkondor/wstroke/tree/no_vala) branch instead) - Optional, but highly recommended: [WCM](https://github.com/WayfireWM/wcm) for basic configuration - Optionally [libinput](https://www.freedesktop.org/wiki/Software/libinput/) version [1.17](https://lists.freedesktop.org/archives/wayland-devel/2021-February/041733.html) or higher for improved touchpad support (to allow tap-and-drag for the right and middle buttons, required for drawing gestures without physical buttons) ### Building and installing ``` meson build ninja -C build sudo ninja -C build install ``` If you get build errors, your Wayfire version might be too old (or too new). For older Wayfire versions, try the following: - For version 0.7.0, use the [wayfire-0.7 branch](https://github.com/dkondor/wstroke/tree/wayfire-0.7) (run `git checkout wayfire-0.7` before building). - For older Wayfire versions of the 0.8.0 series (between commits [3cca6c9](https://github.com/WayfireWM/wayfire/commit/3cca6c9fee35ea8671da2b1c3f56ca61045ea693) and [d1f33e5](https://github.com/WayfireWM/wayfire/commit/d1f33e58326175f6437d0345ac78b0bb9f03b889)), use [this state](https://github.com/dkondor/wstroke/tree/4f2e8f00e4c734ac6fc3698bc4cfc504fe47a311) (run `git checkout 4f2e8f0` before building). If using multiple monitors, you can separately apply the fix to [issue #5](https://github.com/dkondor/wstroke/issues/5): `git cherry-pick 1c02905a4e` - For moderately old versions of Wayfire (between commits [d1f33e5](https://github.com/WayfireWM/wayfire/commit/d1f33e58326175f6437d0345ac78b0bb9f03b889) and [3ac0284](https://github.com/WayfireWM/wayfire/commit/3ac028406cc3697dd40c128721fb6e681b00c337)), use [this state](https://github.com/dkondor/wstroke/tree/0401b4f608c7d265a10fa2e7f4ce2dafb9caca4b) (run `git checkout 0401b4f` before building). If using multiple monitors, you can separately apply the fix to [issue #5](https://github.com/dkondor/wstroke/issues/5): `git cherry-pick 1c02905a4e` - For recent versions of Wayfire (0.8.0 or newer), use this branch (and report issues for build failures). The current version supports building against wlroots versions 0.16-0.18. However, the version of wlroots should be the same that was used for building Wayfire (this should be detected during compilation). If you would like to (or need to) use wlroots 0.18, you need to use the [track-wlroots](https://github.com/WayfireWM/wayfire/tree/track-wlroots) branch of Wayfire. ### Running If correctly installed, it will show up as "Mouse Gestures" plugin in WCM and can be enabled from there, or with adding `wstroke` to the list of plugins (in `[core]`) in `~/.config/wayfire.ini`. ### Configuration Basic options such as the button used for gestures, or the target of gestures can be changed with WCM as the "Mouse Gestures" plugin, or by manually editing the `[wstroke]` section in `~/.config/wayfire.ini`. Note: trying to set a button will likely make WCM to show a warning (e.g. "Attempting to bind `BTN_RIGHT` without modifier"); it is safe to ignore this, since wstroke will forward button clicks when needed. Gestures can be configured by the standalone program `wstroke-config`. Recommended ways to obtain an initial configuration: - If you have Easystroke installed, `wstroke-config` will attempt to import gestures from it, by looking for any of the `actions*` files under `~/.easystroke`. Even without Easystroke installed, copying the content of this directory from a previous installation can be used to import gestures. It is recommended to check that importing is done correctly. - An example configuration file is under [example/actions-wstroke-2](example/actions-wstroke-2). This is installed automatically and will be used by `wstroke-config` as default if no other configuration exists. You can copy this file to `~/.config/wstroke` manually as well. Gestures are stored under `wstroke/actions-wstroke-2` in the directory given by the `XDG_CONFIG_HOME` environment variable (`~/.config` by default). It is recommended not to edit this file manually, but it can be copied between different computers, or backed up and restored manually. #### Focus settings #### For a better experience, it is recommended to diable the "click-to-focus" feature in Wayfire for the mouse button used for gestures. This will allow wstroke to manage focus when using this button and set the target of the gesture as requested by the user. To do this, under the "Core" tab of WCM, change the option "Mouse button to focus views" to *not* include the button used for gestures. E.g. if the right button is used, the setting here should not contain `BTN_RIGHT`, so it might look like `BTN_LEFT | BTN_MIDDLE` (note: it is best to set this by clicking on the edit button on the right of the setting and manually editing the text that corresponds to this setting). Don't be alarmed by the warning that appears ("Attempting to bind `BTN_LEFT | BTN_MIDDLE` without modifier"); this is exactly the intended behavior in this case. The same can be achieved by editing the option `focus_buttons` in the `[core]` section of `~/.config/wayfire.ini`. ### What works - Importing saved strokes from "actions" files created with Easystroke (just run `wstroke-config`). - Drawing and recognizing strokes. - Actions on the active view: close, minimize, (un)maximize, move, resize (select "WM Action" and the appropriate action). - Actions to activate another Wayfire plugin (typical desktop interactions are under "Global Action"; "Custom Plugin" can be used with giving the plugin activator name directly), only supported for some plugins, see [here](https://github.com/WayfireWM/wayfire/issues/1811). - Generating keypresses ("Key" action). - Generating mouse clicks ("Button" action). - Generating modifiers ("Ignore" action -- only works in combination with mouse clicks, not the keyboard). - Emulating touchpad "gestures" with mouse movement, such as scrolling or pinch zoom in apps that support it ("Touchpad Gesture" action; "Scroll" action from Easystroke will be converted to this). - Running commands as a gesture action. - Getting keybindings and mouse button bindings in the configuration for actions. - Recording strokes (slight change: these have to be recorded on a "canvas", cannot be drawn anywhere like with Easystroke; also, recording strokes requires using a different mouse button). - Identifying views and using application specific gestures or excluding certain apps completely; setting these in `wstroke-config` by interactively grabbing the app-id of an open view. - Option to target either the view under the mouse when starting the gesture (original Easystroke behavior) or the currently active one. - Option to change focus to the view under the mouse after a gesture. - Basic timeouts (move the mouse after clicking to have a gesture / end the gesture if not moving within a timeout). ### What does not work - SendText action (removed from settings, will be converted to Global) - Ignore in combination with keyboard keypresses. - Individual settings (which button, timeout) for each pointing device - Advanced gestures - Touchscreen and pen / stylus support wstroke-2.2.1/example/000077500000000000000000000000001472137506600146605ustar00rootroot00000000000000wstroke-2.2.1/example/actions-wstroke-2000066400000000000000000002740701472137506600201100ustar00rootroot0000000000000022 serialization::archive 18 0 5 1 1 0 0 0 0 0 21 0 0 0 1 0 4 1 6 1 58 4.83333333333333282e-01 0.00000000000000000e+00 4.89999999999999991e-01 6.66666666666665408e-03 4.89999999999999991e-01 1.33333333333333082e-02 4.89999999999999991e-01 2.00000000000000178e-02 4.89999999999999991e-01 2.66666666666666718e-02 4.89999999999999991e-01 3.33333333333333259e-02 4.89999999999999991e-01 3.99999999999999800e-02 4.89999999999999991e-01 5.33333333333333437e-02 4.96666666666666590e-01 5.99999999999999978e-02 4.96666666666666590e-01 8.00000000000000155e-02 5.03333333333333299e-01 9.33333333333333237e-02 5.03333333333333299e-01 1.13333333333333341e-01 5.03333333333333299e-01 1.19999999999999996e-01 5.03333333333333299e-01 1.26666666666666705e-01 5.03333333333333299e-01 1.33333333333333304e-01 5.16666666666666718e-01 2.80000000000000027e-01 5.16666666666666718e-01 2.99999999999999989e-01 5.16666666666666718e-01 3.66666666666666641e-01 5.16666666666666718e-01 3.86666666666666659e-01 5.16666666666666718e-01 4.40000000000000002e-01 5.16666666666666718e-01 4.53333333333333310e-01 5.16666666666666718e-01 4.66666666666666674e-01 5.16666666666666718e-01 4.73333333333333328e-01 5.16666666666666718e-01 4.86666666666666692e-01 5.16666666666666718e-01 4.93333333333333346e-01 5.16666666666666718e-01 5.06666666666666710e-01 5.16666666666666718e-01 5.20000000000000018e-01 5.16666666666666718e-01 5.33333333333333326e-01 5.16666666666666718e-01 5.53333333333333344e-01 5.16666666666666718e-01 5.66666666666666652e-01 5.16666666666666718e-01 6.33333333333333304e-01 5.16666666666666718e-01 6.53333333333333321e-01 5.16666666666666718e-01 7.19999999999999973e-01 5.16666666666666718e-01 7.33333333333333282e-01 5.16666666666666718e-01 7.53333333333333299e-01 5.16666666666666718e-01 7.66666666666666718e-01 5.16666666666666718e-01 7.73333333333333317e-01 5.16666666666666718e-01 7.80000000000000027e-01 5.16666666666666718e-01 7.86666666666666625e-01 5.16666666666666718e-01 7.93333333333333335e-01 5.16666666666666718e-01 8.00000000000000044e-01 5.16666666666666718e-01 8.19999999999999951e-01 5.16666666666666718e-01 8.73333333333333295e-01 5.03333333333333299e-01 9.13333333333333330e-01 5.03333333333333299e-01 9.26666666666666639e-01 5.03333333333333299e-01 9.33333333333333348e-01 5.03333333333333299e-01 9.39999999999999947e-01 5.03333333333333299e-01 9.46666666666666656e-01 4.96666666666666590e-01 9.46666666666666656e-01 4.96666666666666590e-01 9.53333333333333366e-01 4.96666666666666590e-01 9.59999999999999964e-01 4.96666666666666590e-01 9.66666666666666674e-01 4.96666666666666590e-01 9.73333333333333384e-01 4.89999999999999991e-01 9.79999999999999982e-01 4.89999999999999991e-01 9.86666666666666692e-01 4.89999999999999991e-01 9.93333333333333290e-01 4.83333333333333282e-01 9.93333333333333290e-01 4.83333333333333282e-01 1.00000000000000000e+00 0 0 8 4 View 1 0 2 0 0 3 9 Move View 2 3 108 9.73208306950853475e-01 0.00000000000000000e+00 9.73443227614696260e-01 7.05412741289879186e-04 9.73338131528240336e-01 8.10508827745914928e-04 9.73338131528240336e-01 8.84856987978688281e-04 9.73644634665830289e-01 2.11103222577890826e-03 9.73951137803420242e-01 3.33704477613883110e-03 9.74403408887549727e-01 6.05050859347600056e-03 9.75076609515653447e-01 1.21094769338493391e-02 9.75897855714461415e-01 2.11423716835351660e-02 9.77089703902351170e-01 4.02119426897700194e-02 9.78281552090240814e-01 5.92815136960048727e-02 9.78281552090240814e-01 7.60093616860665899e-02 9.78281552090240814e-01 9.50416772684648525e-02 9.78281552090240814e-01 1.14074155538303468e-01 9.78281552090240814e-01 1.33106633808142027e-01 9.78281552090240814e-01 1.54592438678223087e-01 9.78281552090240814e-01 1.73624916948061703e-01 9.78281552090240814e-01 1.97712907426291218e-01 9.78281552090240814e-01 2.21800735217080380e-01 9.78281552090240814e-01 2.48639444936483545e-01 9.78281552090240814e-01 2.91462521043620415e-01 9.79770467544180956e-01 3.21237901748499532e-01 9.81333243096033092e-01 3.54061394335483615e-01 9.82896344022765822e-01 3.86884886922467697e-01 9.84088192210655466e-01 4.05954457928702550e-01 9.84088192210655466e-01 4.24986936198541165e-01 9.84088192210655466e-01 4.41714621501162585e-01 9.85131669452960157e-01 4.56323465580869003e-01 9.86471888586434864e-01 4.80448548795494701e-01 9.87737922247116984e-01 5.01971446401971999e-01 9.89004281282679809e-01 5.23494506695889705e-01 9.89004281282679809e-01 5.42526984965728265e-01 9.89004281282679809e-01 5.55091335982752510e-01 9.89004281282679809e-01 5.67655686999776754e-01 9.89004281282679809e-01 5.91743677478006269e-01 9.89004281282679809e-01 6.18582387197409433e-01 9.89004281282679809e-01 6.45421096916812709e-01 9.89004281282679809e-01 6.75159222197855291e-01 9.89004281282679809e-01 7.04897510166338170e-01 9.89004281282679809e-01 7.31736219885741335e-01 9.89004281282679809e-01 7.61474345166783917e-01 9.89004281282679809e-01 7.91212633135266796e-01 9.89004281282679809e-01 8.15300460926055903e-01 9.89004281282679809e-01 8.42139170645459179e-01 9.89004281282679809e-01 8.66226998436248286e-01 9.89004281282679809e-01 8.87713128681210106e-01 9.89004281282679809e-01 9.02284717337079933e-01 9.89004281282679809e-01 9.12990527035724142e-01 9.89004281282679809e-01 9.20425017684124769e-01 9.89004281282679809e-01 9.26447055975542222e-01 9.89004281282679809e-01 9.30089953139509706e-01 9.88624894171820090e-01 9.31985261819405220e-01 9.88089001743358564e-01 9.32789588524418400e-01 9.87383589002068796e-01 9.33024509188261186e-01 9.85488280322173171e-01 9.32645447452282172e-01 9.82774979192276410e-01 9.32193176368152576e-01 9.77870278241075530e-01 9.30967163817792653e-01 9.70288392771731956e-01 9.29450916873876265e-01 9.61255660709486426e-01 9.28629670675068297e-01 9.52222603272360191e-01 9.27808424476260329e-01 9.41479700837319688e-01 9.28703856147860773e-01 9.30737123777159781e-01 9.29598962444580512e-01 9.19994221342119278e-01 9.30494068741300362e-01 9.03118327781353258e-01 9.32744361416005474e-01 8.86242434220587239e-01 9.34994328715829992e-01 8.69366866034701813e-01 9.37244621390535104e-01 8.50002830761461770e-01 9.40875154309037232e-01 8.28368654945795813e-01 9.43420562000508944e-01 8.09299083939560959e-01 9.44612410188398588e-01 7.85174163412375559e-01 9.45952629321873184e-01 7.61086010246705746e-01 9.45952629321873184e-01 7.28299610396117902e-01 9.45952629321873184e-01 6.92316402342959436e-01 9.45952629321873184e-01 6.65477855310996569e-01 9.45952629321873184e-01 6.38638982904152996e-01 9.45952629321873184e-01 6.14551155113363889e-01 9.45952629321873184e-01 5.90463327322574671e-01 9.45952629321873184e-01 5.60725039354091792e-01 9.45952629321873184e-01 5.17902125934395330e-01 9.45952629321873184e-01 4.88163837965912450e-01 9.45952629321873184e-01 4.58388457261033277e-01 9.47441219400932733e-01 4.28613076556154160e-01 9.48930134854873097e-01 3.98726743016966978e-01 9.51918703133815658e-01 3.68655921920440610e-01 9.56429375104526747e-01 3.35536663566928628e-01 9.61160651244331143e-01 2.89102088971808890e-01 9.68590261269521635e-01 2.55726435212329850e-01 9.74947435688027886e-01 2.25655614115803538e-01 9.79458107658738975e-01 1.89339222184840283e-01 9.84410313342517607e-01 1.65103023448466169e-01 9.87103115854932267e-01 1.43469173007680806e-01 9.89648523546403980e-01 1.19232974271306691e-01 9.92341326058818640e-01 1.06520577683578144e-01 9.94297154466518673e-01 9.56673731639908764e-02 9.96105913428156242e-01 8.66343157268646968e-02 9.96927159626964210e-01 7.58914132918241946e-02 9.97822265923683949e-01 6.33270622747998946e-02 9.97822265923683949e-01 5.26212525761556305e-02 9.97822265923683949e-01 3.80496639202857478e-02 9.97822265923683949e-01 2.90540245944364561e-02 9.97822265923683949e-01 2.29948935666227650e-02 9.98495466551787669e-01 1.69357625388090738e-02 9.99168992554771984e-01 1.42224614089122570e-02 9.99620938264020875e-01 1.23271527290167437e-02 1.00000000000000000e+00 1.11008148037761711e-02 9.99693496862410047e-01 1.09957187173201354e-02 9.99588400775954011e-01 1.09957187173201354e-02 9.99291008135022807e-01 1.14164284380248726e-02 9.98870623789198664e-01 8 4 1 10 Close view 3 5 111 0.00000000000000000e+00 8.57000255343282724e-01 1.21279077376713618e-02 8.62367853435522025e-01 2.72881131927790177e-02 8.65051652481641620e-01 4.71307242125739156e-02 8.67978733369004440e-01 6.55901188565239934e-02 8.71246614135239517e-01 9.64807536604550542e-02 8.71246614135239517e-01 1.21648106161215297e-01 8.71246614135239517e-01 1.65963638603285113e-01 8.71246614135239517e-01 1.99168530416004241e-01 8.75445278908003277e-01 2.48749267010802688e-01 8.75445278908003277e-01 2.84962590284380535e-01 8.75445278908003277e-01 3.43291464733756491e-01 8.75445278908003277e-01 3.81442830692005963e-01 8.75445278908003277e-01 4.36494072571383840e-01 8.75445278908003277e-01 5.07196959490237242e-01 8.75445278908003277e-01 6.13123361600732886e-01 8.75445278908003277e-01 6.42835311281425459e-01 8.75445278908003277e-01 6.78584397370605719e-01 8.75445278908003277e-01 7.07243152244903750e-01 8.75445278908003277e-01 7.47542737921557010e-01 8.75445278908003277e-01 7.76207651829810441e-01 8.75445278908003277e-01 8.10302780675517753e-01 8.75445278908003277e-01 8.26916004891299461e-01 8.75445278908003277e-01 8.52191140486281418e-01 8.75445278908003277e-01 8.64192531401449693e-01 8.75445278908003277e-01 8.83320181229574120e-01 8.75445278908003277e-01 8.93558805301388426e-01 8.75445278908003277e-01 9.14545456878421925e-01 8.75445278908003277e-01 9.26560448993575370e-01 8.78990316202236999e-01 9.42701223979685476e-01 8.78990316202236999e-01 9.52392207282287795e-01 8.81849647716089091e-01 9.58597433992478321e-01 8.81849647716089091e-01 9.61629475083499830e-01 8.81849647716089091e-01 9.67693557265542847e-01 8.81849647716089091e-01 9.73757639447585976e-01 8.81849647716089091e-01 9.76789680538607485e-01 8.76482049623849790e-01 9.79821721629629105e-01 8.65746853439371411e-01 9.82853762720650614e-01 8.52328114835188111e-01 9.85902997781464618e-01 8.36133961554796801e-01 9.89362578479565435e-01 8.20823116394193586e-01 9.89362578479565435e-01 8.01503253381373848e-01 9.93031822958568955e-01 7.88511541131440175e-01 9.96558896403765715e-01 7.72902496076823708e-01 1.00000000000000000e+00 7.60719157033552640e-01 1.00000000000000000e+00 7.47176211244599653e-01 1.00000000000000000e+00 7.33813160572430845e-01 1.00000000000000000e+00 7.20813236277223268e-01 1.00000000000000000e+00 7.14838973340364947e-01 1.00000000000000000e+00 7.04104033782301242e-01 1.00000000000000000e+00 6.93291079794134291e-01 1.00000000000000000e+00 6.78858923477852483e-01 1.00000000000000000e+00 6.67518088954414113e-01 1.00000000000000000e+00 6.50314623984561302e-01 1.00000000000000000e+00 6.41807458333493730e-01 1.00000000000000000e+00 6.28388719729310430e-01 1.00000000000000000e+00 6.17653523544831939e-01 1.00000000000000000e+00 6.06872391232101904e-01 1.00000000000000000e+00 5.98786606154158019e-01 1.00000000000000000e+00 5.88049356958361136e-01 1.00000000000000000e+00 5.77312107762564142e-01 1.00000000000000000e+00 5.66576911578085651e-01 1.00000000000000000e+00 5.58525771066141652e-01 1.00000000000000000e+00 5.47758753206226245e-01 1.00000000000000000e+00 5.39683233184874989e-01 1.00000000000000000e+00 5.28878491241982052e-01 1.00000000000000000e+00 5.20774998941415990e-01 1.00000000000000000e+00 5.07329057937262573e-01 1.00000000000000000e+00 4.91193928732278351e-01 1.00000000000000000e+00 4.75091391081975456e-01 1.00000000000000000e+00 4.64356194897497021e-01 1.00000000000000000e+00 4.53621255339433316e-01 1.00000000000000000e+00 4.42886059154954881e-01 1.00000000000000000e+00 4.37518589375923028e-01 1.00000000000000000e+00 4.26783521504651986e-01 1.00000000000000000e+00 4.16048453633380944e-01 1.00000000000000000e+00 4.05313385762109901e-01 1.00000000000000000e+00 3.97262116936958454e-01 1.00000000000000000e+00 3.86527049065687356e-01 1.00000000000000000e+00 3.78475651927328516e-01 1.00000000000000000e+00 3.67740584056057473e-01 1.00000000000000000e+00 3.62373114277025621e-01 1.00000000000000000e+00 3.51638046405754578e-01 1.00000000000000000e+00 3.43586777580603131e-01 1.00000000000000000e+00 3.32851709709332089e-01 1.00000000000000000e+00 3.27484111617092843e-01 1.00000000000000000e+00 3.14065244699702206e-01 1.00000000000000000e+00 3.06013975874550759e-01 1.00000000000000000e+00 2.92595108957160066e-01 1.00000000000000000e+00 2.84543840132008619e-01 1.00000000000000000e+00 2.73808772260737576e-01 1.00000000000000000e+00 2.65757503435586129e-01 1.00000000000000000e+00 2.60390033656554276e-01 1.00000000000000000e+00 2.55022435564315086e-01 1.00000000000000000e+00 2.49654965785283234e-01 1.00000000000000000e+00 2.38919897914012191e-01 1.00000000000000000e+00 2.30868500775653351e-01 1.00000000000000000e+00 2.20133432904382254e-01 1.00000000000000000e+00 2.12082164079230806e-01 1.00000000000000000e+00 2.01347096207959764e-01 1.00000000000000000e+00 1.93295827382808316e-01 1.00000000000000000e+00 1.87928229290569071e-01 1.00000000000000000e+00 1.82560759511537274e-01 1.00000000000000000e+00 1.77193161419298029e-01 1.00000000000000000e+00 1.66458093548026986e-01 1.00000000000000000e+00 1.58406824722875539e-01 1.00000000000000000e+00 1.47671756851604496e-01 1.00000000000000000e+00 1.42304287072572644e-01 1.00000000000000000e+00 1.36936688980333399e-01 1.00000000000000000e+00 1.31569219201301602e-01 9.96967958908978491e-01 1.23517950376150154e-01 9.96967958908978491e-01 1.18150352283910909e-01 8 6 2 13 Maximize view 4 7 64 0.00000000000000000e+00 9.19946856898660181e-02 1.66636343506401108e-02 9.19946856898660181e-02 2.99945418311521883e-02 9.19946856898660181e-02 6.04310966124093629e-02 9.19946856898660181e-02 8.83958201991738024e-02 9.19946856898660181e-02 1.31522970146496110e-01 9.19946856898660181e-02 1.62123693107052047e-01 9.19946856898660181e-02 2.15488506111879752e-01 9.19946856898660181e-02 2.54869970057642226e-01 9.19946856898660181e-02 3.12342654531761021e-01 9.19946856898660181e-02 3.60336460144789539e-01 9.19946856898660181e-02 4.21463438025807102e-01 9.19946856898660181e-02 4.90572599992383951e-01 9.19946856898660181e-02 5.68013720172235503e-01 9.19946856898660181e-02 6.21066557221213844e-01 9.19946856898660181e-02 6.50764637271536106e-01 9.19946856898660181e-02 6.89973470760716467e-01 9.19946856898660181e-02 7.21985701572150029e-01 9.19946856898660181e-02 7.66615187813192378e-01 9.19946856898660181e-02 7.91701326744040035e-01 9.19946856898660181e-02 8.28535363151577720e-01 9.19946856898660181e-02 8.52781197948179859e-01 9.19946856898660181e-02 8.87902751509458743e-01 9.19946856898660181e-02 9.03563041850192317e-01 9.19946856898660181e-02 9.23772370382593633e-01 9.19946856898660181e-02 9.36321927593934844e-01 9.19946856898660181e-02 9.43806529775227832e-01 9.19946856898660181e-02 9.50471983515483898e-01 9.19946856898660181e-02 9.57137437255739854e-01 9.19946856898660181e-02 9.63802890995995920e-01 9.19946856898660181e-02 9.70468344736251987e-01 1.00844253196977873e-01 9.77133798476508053e-01 1.12644052641006187e-01 9.84152411255392989e-01 1.31281372130053919e-01 9.87986387016610013e-01 1.44856839424057415e-01 9.95776759483744667e-01 1.68989843856880317e-01 9.95776759483744667e-01 1.87454532889345882e-01 1.00000000000000000e+00 2.13621586705198263e-01 1.00000000000000000e+00 2.36840101039590745e-01 1.00000000000000000e+00 2.70220400011847162e-01 1.00000000000000000e+00 2.96085350528680791e-01 1.00000000000000000e+00 3.38912935860169351e-01 1.00000000000000000e+00 3.67411347067609362e-01 1.00000000000000000e+00 4.19088217830300314e-01 1.00000000000000000e+00 4.54122327859650288e-01 1.00000000000000000e+00 4.98850822744908884e-01 1.00000000000000000e+00 5.32948440754891561e-01 1.00000000000000000e+00 5.88784802878302593e-01 1.00000000000000000e+00 6.27119201048194075e-01 1.00000000000000000e+00 6.73589796750203473e-01 1.00000000000000000e+00 7.05077084294154388e-01 1.00000000000000000e+00 7.47430500021860844e-01 1.00000000000000000e+00 7.68606784771849960e-01 1.00000000000000000e+00 7.94390779502672006e-01 1.00000000000000000e+00 8.11590358081263230e-01 1.00000000000000000e+00 8.27387455238079106e-01 1.00000000000000000e+00 8.33287354960093318e-01 1.00000000000000000e+00 8.39186972606198012e-01 1.00000000000000000e+00 8.48036822189219386e-01 1.00000000000000000e+00 8.56950985079594774e-01 1.00000000000000000e+00 8.70359181359295575e-01 1.00000000000000000e+00 8.80959029884532230e-01 1.00000000000000000e+00 8.93255847081008025e-01 1.00000000000000000e+00 9.02105696664029288e-01 1.00000000000000000e+00 9.08005314310133982e-01 8 8 5 12 Mnimize view 5 9 91 0.00000000000000000e+00 5.28571428571428581e-01 9.52380952380952328e-02 5.09523809523809490e-01 1.23809523809523814e-01 5.09523809523809490e-01 2.19047619047619047e-01 4.90476190476190455e-01 2.38095238095238082e-01 4.90476190476190455e-01 2.57142857142857117e-01 4.80952380952380965e-01 2.76190476190476208e-01 4.80952380952380965e-01 2.95238095238095244e-01 4.80952380952380965e-01 3.14285714285714279e-01 4.80952380952380965e-01 3.71428571428571441e-01 4.61904761904761929e-01 3.90476190476190477e-01 4.61904761904761929e-01 4.19047619047619058e-01 4.61904761904761929e-01 4.38095238095238093e-01 4.61904761904761929e-01 4.66666666666666674e-01 4.61904761904761929e-01 4.85714285714285710e-01 4.61904761904761929e-01 5.61904761904761907e-01 4.42857142857142838e-01 5.71428571428571397e-01 4.42857142857142838e-01 5.90476190476190488e-01 4.42857142857142838e-01 5.99999999999999978e-01 4.42857142857142838e-01 6.09523809523809579e-01 4.42857142857142838e-01 6.19047619047619069e-01 4.42857142857142838e-01 6.28571428571428559e-01 4.42857142857142838e-01 6.38095238095238049e-01 4.42857142857142838e-01 6.47619047619047650e-01 4.42857142857142838e-01 6.57142857142857140e-01 4.42857142857142838e-01 6.76190476190476231e-01 4.42857142857142838e-01 6.95238095238095211e-01 4.42857142857142838e-01 7.14285714285714302e-01 4.42857142857142838e-01 7.33333333333333282e-01 4.42857142857142838e-01 7.52380952380952372e-01 4.52380952380952384e-01 7.61904761904761862e-01 4.52380952380952384e-01 7.80952380952380953e-01 4.61904761904761929e-01 7.90476190476190443e-01 4.61904761904761929e-01 8.00000000000000044e-01 4.61904761904761929e-01 8.09523809523809534e-01 4.61904761904761929e-01 8.28571428571428625e-01 4.71428571428571419e-01 8.38095238095238115e-01 4.71428571428571419e-01 8.57142857142857095e-01 4.71428571428571419e-01 8.66666666666666696e-01 4.71428571428571419e-01 8.85714285714285676e-01 4.80952380952380965e-01 8.95238095238095277e-01 4.80952380952380965e-01 9.04761904761904767e-01 4.80952380952380965e-01 9.14285714285714257e-01 4.80952380952380965e-01 9.33333333333333348e-01 4.80952380952380965e-01 9.42857142857142838e-01 4.80952380952380965e-01 9.61904761904761929e-01 4.80952380952380965e-01 9.71428571428571419e-01 4.80952380952380965e-01 9.80952380952380909e-01 4.80952380952380965e-01 9.90476190476190510e-01 4.90476190476190455e-01 1.00000000000000000e+00 4.90476190476190455e-01 9.90476190476190510e-01 4.90476190476190455e-01 9.71428571428571419e-01 4.90476190476190455e-01 9.52380952380952328e-01 4.90476190476190455e-01 9.33333333333333348e-01 4.90476190476190455e-01 9.14285714285714257e-01 4.90476190476190455e-01 8.95238095238095277e-01 4.90476190476190455e-01 8.66666666666666696e-01 4.90476190476190455e-01 8.57142857142857095e-01 4.90476190476190455e-01 8.28571428571428625e-01 4.90476190476190455e-01 8.09523809523809534e-01 4.90476190476190455e-01 7.90476190476190443e-01 4.90476190476190455e-01 7.71428571428571463e-01 4.90476190476190455e-01 6.19047619047619069e-01 4.90476190476190455e-01 4.85714285714285710e-01 5.09523809523809490e-01 3.52380952380952406e-01 5.09523809523809490e-01 2.95238095238095244e-01 5.28571428571428581e-01 2.76190476190476208e-01 5.28571428571428581e-01 2.66666666666666663e-01 5.28571428571428581e-01 2.57142857142857117e-01 5.28571428571428581e-01 2.47619047619047628e-01 5.38095238095238071e-01 2.38095238095238082e-01 5.38095238095238071e-01 2.28571428571428537e-01 5.38095238095238071e-01 2.19047619047619047e-01 5.47619047619047672e-01 2.00000000000000011e-01 5.47619047619047672e-01 1.80952380952380976e-01 5.57142857142857162e-01 1.61904761904761885e-01 5.57142857142857162e-01 1.52380952380952395e-01 5.57142857142857162e-01 1.33333333333333304e-01 5.57142857142857162e-01 1.23809523809523814e-01 5.57142857142857162e-01 1.14285714285714268e-01 5.57142857142857162e-01 1.04761904761904778e-01 5.57142857142857162e-01 1.04761904761904778e-01 5.47619047619047672e-01 9.52380952380952328e-02 5.47619047619047672e-01 8.57142857142857428e-02 5.47619047619047672e-01 7.61904761904761973e-02 5.38095238095238071e-01 6.66666666666666519e-02 5.38095238095238071e-01 5.71428571428571619e-02 5.38095238095238071e-01 4.76190476190476164e-02 5.38095238095238071e-01 3.80952380952380709e-02 5.38095238095238071e-01 2.85714285714285809e-02 5.38095238095238071e-01 1.90476190476190355e-02 5.38095238095238071e-01 8 10 4 11 Resize view 6 11 110 1.00000000000000000e+00 4.95098039215686292e-01 9.80392156862745057e-01 4.95098039215686292e-01 9.50980392156862697e-01 4.95098039215686292e-01 8.72549019607843146e-01 4.95098039215686292e-01 8.52941176470588203e-01 4.95098039215686292e-01 8.33333333333333370e-01 4.85294117647058820e-01 8.13725490196078427e-01 4.85294117647058820e-01 7.94117647058823484e-01 4.85294117647058820e-01 7.64705882352941124e-01 4.85294117647058820e-01 7.45098039215686292e-01 4.85294117647058820e-01 7.25490196078431349e-01 4.85294117647058820e-01 7.05882352941176516e-01 4.85294117647058820e-01 6.47058823529411797e-01 5.04901960784313708e-01 6.27450980392156854e-01 5.04901960784313708e-01 6.17647058823529438e-01 5.04901960784313708e-01 6.07843137254901911e-01 5.04901960784313708e-01 5.98039215686274495e-01 5.04901960784313708e-01 5.00000000000000000e-01 5.04901960784313708e-01 4.01960784313725505e-01 5.04901960784313708e-01 3.23529411764705899e-01 5.04901960784313708e-01 2.25490196078431349e-01 5.04901960784313708e-01 2.05882352941176461e-01 4.95098039215686292e-01 1.86274509803921573e-01 4.95098039215686292e-01 1.66666666666666630e-01 4.95098039215686292e-01 1.56862745098039214e-01 4.95098039215686292e-01 1.37254901960784326e-01 4.95098039215686292e-01 7.84313725490196068e-02 5.14705882352941124e-01 5.88235294117647189e-02 5.14705882352941124e-01 4.90196078431372473e-02 5.14705882352941124e-01 2.94117647058823595e-02 5.24509803921568651e-01 1.96078431372548878e-02 5.24509803921568651e-01 9.80392156862747166e-03 5.24509803921568651e-01 0.00000000000000000e+00 5.24509803921568651e-01 0.00000000000000000e+00 5.14705882352941124e-01 9.80392156862747166e-03 5.14705882352941124e-01 1.96078431372548878e-02 5.14705882352941124e-01 2.94117647058823595e-02 5.14705882352941124e-01 3.92156862745097756e-02 5.14705882352941124e-01 4.90196078431372473e-02 5.14705882352941124e-01 5.88235294117647189e-02 5.04901960784313708e-01 6.86274509803921351e-02 5.04901960784313708e-01 8.82352941176470784e-02 5.04901960784313708e-01 9.80392156862744946e-02 5.04901960784313708e-01 1.07843137254901966e-01 5.04901960784313708e-01 1.27450980392156854e-01 5.04901960784313708e-01 1.37254901960784326e-01 5.04901960784313708e-01 1.47058823529411797e-01 5.04901960784313708e-01 1.56862745098039214e-01 5.04901960784313708e-01 1.66666666666666630e-01 5.04901960784313708e-01 1.86274509803921573e-01 5.04901960784313708e-01 2.45098039215686292e-01 4.85294117647058820e-01 2.64705882352941180e-01 4.85294117647058820e-01 2.94117647058823539e-01 4.85294117647058820e-01 3.03921568627450955e-01 4.85294117647058820e-01 3.23529411764705899e-01 4.85294117647058820e-01 3.33333333333333315e-01 4.85294117647058820e-01 3.43137254901960786e-01 4.85294117647058820e-01 3.52941176470588258e-01 4.75490196078431349e-01 3.62745098039215674e-01 4.75490196078431349e-01 3.72549019607843146e-01 4.75490196078431349e-01 3.92156862745098034e-01 4.75490196078431349e-01 4.11764705882352922e-01 4.75490196078431349e-01 4.21568627450980393e-01 4.75490196078431349e-01 4.80392156862745112e-01 4.95098039215686292e-01 4.90196078431372528e-01 4.95098039215686292e-01 5.09803921568627416e-01 4.95098039215686292e-01 5.19607843137254943e-01 4.95098039215686292e-01 5.29411764705882359e-01 5.04901960784313708e-01 5.39215686274509776e-01 5.04901960784313708e-01 5.49019607843137303e-01 5.04901960784313708e-01 5.58823529411764719e-01 5.04901960784313708e-01 5.68627450980392135e-01 5.04901960784313708e-01 5.78431372549019662e-01 5.04901960784313708e-01 5.88235294117647078e-01 5.04901960784313708e-01 5.98039215686274495e-01 5.04901960784313708e-01 6.07843137254901911e-01 5.14705882352941124e-01 6.17647058823529438e-01 5.14705882352941124e-01 6.27450980392156854e-01 5.14705882352941124e-01 6.37254901960784270e-01 5.14705882352941124e-01 6.47058823529411797e-01 5.14705882352941124e-01 6.56862745098039214e-01 5.14705882352941124e-01 6.66666666666666630e-01 5.14705882352941124e-01 6.86274509803921573e-01 5.14705882352941124e-01 6.96078431372548989e-01 5.14705882352941124e-01 7.05882352941176516e-01 5.14705882352941124e-01 7.15686274509803932e-01 5.14705882352941124e-01 7.35294117647058876e-01 5.14705882352941124e-01 7.45098039215686292e-01 5.14705882352941124e-01 7.64705882352941124e-01 5.04901960784313708e-01 7.74509803921568651e-01 5.04901960784313708e-01 7.84313725490196068e-01 5.04901960784313708e-01 7.94117647058823484e-01 4.95098039215686292e-01 8.03921568627451011e-01 4.85294117647058820e-01 8.13725490196078427e-01 4.85294117647058820e-01 8.23529411764705843e-01 4.85294117647058820e-01 8.33333333333333370e-01 4.85294117647058820e-01 8.43137254901960786e-01 4.75490196078431349e-01 8.52941176470588203e-01 4.75490196078431349e-01 8.62745098039215730e-01 4.75490196078431349e-01 8.72549019607843146e-01 4.75490196078431349e-01 8.92156862745098089e-01 4.75490196078431349e-01 9.01960784313725505e-01 4.75490196078431349e-01 9.11764705882352922e-01 4.75490196078431349e-01 9.21568627450980338e-01 4.75490196078431349e-01 9.31372549019607865e-01 4.75490196078431349e-01 9.41176470588235281e-01 4.85294117647058820e-01 9.50980392156862697e-01 4.85294117647058820e-01 9.60784313725490224e-01 4.85294117647058820e-01 9.60784313725490224e-01 4.95098039215686292e-01 9.70588235294117641e-01 4.95098039215686292e-01 10 6 Global 1 0 12 1 4 Expo 7 13 147 9.89130434782608647e-01 0.00000000000000000e+00 9.78260869565217406e-01 0.00000000000000000e+00 9.56521739130434812e-01 0.00000000000000000e+00 9.45652173913043459e-01 0.00000000000000000e+00 9.34782608695652217e-01 0.00000000000000000e+00 9.23913043478260865e-01 0.00000000000000000e+00 9.13043478260869512e-01 0.00000000000000000e+00 9.02173913043478271e-01 0.00000000000000000e+00 8.80434782608695676e-01 0.00000000000000000e+00 8.69565217391304324e-01 0.00000000000000000e+00 8.58695652173913082e-01 0.00000000000000000e+00 8.47826086956521729e-01 0.00000000000000000e+00 8.36956521739130488e-01 0.00000000000000000e+00 8.15217391304347783e-01 0.00000000000000000e+00 8.04347826086956541e-01 0.00000000000000000e+00 7.93478260869565188e-01 0.00000000000000000e+00 7.71739130434782594e-01 0.00000000000000000e+00 7.60869565217391353e-01 1.08695652173912971e-02 7.50000000000000000e-01 1.08695652173912971e-02 7.39130434782608647e-01 1.08695652173912971e-02 7.28260869565217406e-01 1.08695652173912971e-02 7.17391304347826053e-01 1.08695652173912971e-02 7.06521739130434812e-01 1.08695652173912971e-02 6.95652173913043459e-01 1.08695652173912971e-02 6.84782608695652217e-01 1.08695652173912971e-02 6.73913043478260865e-01 1.08695652173912971e-02 6.63043478260869512e-01 1.08695652173912971e-02 6.52173913043478271e-01 1.08695652173912971e-02 6.41304347826086918e-01 1.08695652173912971e-02 6.30434782608695676e-01 1.08695652173912971e-02 6.19565217391304324e-01 1.08695652173912971e-02 6.08695652173913082e-01 1.08695652173912971e-02 5.97826086956521729e-01 1.08695652173912971e-02 5.97826086956521729e-01 2.17391304347825942e-02 5.86956521739130488e-01 2.17391304347825942e-02 5.76086956521739135e-01 2.17391304347825942e-02 5.65217391304347783e-01 2.17391304347825942e-02 5.54347826086956541e-01 2.17391304347825942e-02 5.43478260869565188e-01 2.17391304347825942e-02 5.21739130434782594e-01 3.26086956521738913e-02 5.10869565217391353e-01 3.26086956521738913e-02 5.00000000000000000e-01 3.26086956521738913e-02 4.89130434782608703e-01 4.34782608695651884e-02 4.78260869565217406e-01 4.34782608695651884e-02 4.67391304347826109e-01 4.34782608695651884e-02 4.56521739130434756e-01 4.34782608695651884e-02 4.45652173913043459e-01 4.34782608695651884e-02 4.34782608695652162e-01 5.43478260869565410e-02 4.23913043478260865e-01 5.43478260869565410e-02 4.13043478260869568e-01 5.43478260869565410e-02 4.02173913043478271e-01 5.43478260869565410e-02 3.91304347826086973e-01 5.43478260869565410e-02 3.80434782608695676e-01 5.43478260869565410e-02 3.69565217391304324e-01 5.43478260869565410e-02 3.58695652173913027e-01 5.43478260869565410e-02 3.47826086956521729e-01 5.43478260869565410e-02 3.36956521739130432e-01 5.43478260869565410e-02 3.26086956521739135e-01 5.43478260869565410e-02 3.04347826086956541e-01 5.43478260869565410e-02 2.93478260869565244e-01 5.43478260869565410e-02 2.82608695652173891e-01 5.43478260869565410e-02 2.71739130434782594e-01 5.43478260869565410e-02 2.60869565217391297e-01 5.43478260869565410e-02 2.39130434782608758e-01 4.34782608695651884e-02 2.17391304347826109e-01 4.34782608695651884e-02 2.06521739130434812e-01 4.34782608695651884e-02 2.06521739130434812e-01 3.26086956521738913e-02 1.95652173913043514e-01 3.26086956521738913e-02 1.84782608695652217e-01 3.26086956521738913e-02 1.73913043478260865e-01 3.26086956521738913e-02 1.63043478260869623e-01 2.17391304347825942e-02 1.52173913043478271e-01 2.17391304347825942e-02 1.30434782608695676e-01 2.17391304347825942e-02 1.19565217391304379e-01 2.17391304347825942e-02 1.08695652173913082e-01 2.17391304347825942e-02 9.78260869565217850e-02 2.17391304347825942e-02 8.69565217391304879e-02 2.17391304347825942e-02 7.60869565217391908e-02 2.17391304347825942e-02 6.52173913043478937e-02 2.17391304347825942e-02 5.43478260869565410e-02 3.26086956521738913e-02 5.43478260869565410e-02 4.34782608695651884e-02 5.43478260869565410e-02 5.43478260869565410e-02 5.43478260869565410e-02 7.60869565217391353e-02 5.43478260869565410e-02 8.69565217391304324e-02 5.43478260869565410e-02 9.78260869565217295e-02 5.43478260869565410e-02 1.08695652173913027e-01 5.43478260869565410e-02 1.41304347826086918e-01 5.43478260869565410e-02 1.63043478260869568e-01 5.43478260869565410e-02 1.95652173913043459e-01 5.43478260869565410e-02 2.39130434782608703e-01 5.43478260869565410e-02 2.71739130434782594e-01 5.43478260869565410e-02 2.82608695652173891e-01 5.43478260869565410e-02 2.93478260869565244e-01 5.43478260869565410e-02 3.04347826086956541e-01 5.43478260869565410e-02 3.15217391304347838e-01 5.43478260869565410e-02 3.36956521739130432e-01 5.43478260869565410e-02 3.47826086956521729e-01 5.43478260869565410e-02 3.58695652173913027e-01 5.43478260869565410e-02 3.69565217391304324e-01 5.43478260869565410e-02 3.80434782608695676e-01 5.43478260869565410e-02 3.91304347826086973e-01 5.43478260869565410e-02 4.02173913043478271e-01 5.43478260869565410e-02 4.13043478260869568e-01 5.43478260869565410e-02 4.23913043478260865e-01 5.43478260869565410e-02 4.34782608695652162e-01 4.34782608695652995e-02 4.45652173913043459e-01 4.34782608695652995e-02 4.56521739130434756e-01 4.34782608695652995e-02 4.78260869565217406e-01 4.34782608695652995e-02 5.10869565217391353e-01 4.34782608695652995e-02 5.32608695652173947e-01 4.34782608695652995e-02 5.54347826086956541e-01 4.34782608695652995e-02 5.65217391304347783e-01 4.34782608695652995e-02 5.76086956521739135e-01 4.34782608695652995e-02 5.86956521739130488e-01 4.34782608695652995e-02 5.97826086956521729e-01 3.26086956521739468e-02 6.08695652173913082e-01 3.26086956521739468e-02 6.19565217391304324e-01 3.26086956521739468e-02 6.30434782608695676e-01 3.26086956521739468e-02 6.41304347826086918e-01 3.26086956521739468e-02 6.52173913043478271e-01 3.26086956521739468e-02 6.73913043478260865e-01 3.26086956521739468e-02 6.95652173913043459e-01 2.17391304347826497e-02 7.06521739130434812e-01 2.17391304347826497e-02 7.17391304347826053e-01 2.17391304347826497e-02 7.28260869565217406e-01 2.17391304347826497e-02 7.39130434782608647e-01 2.17391304347826497e-02 7.50000000000000000e-01 2.17391304347826497e-02 7.71739130434782594e-01 2.17391304347826497e-02 7.82608695652173947e-01 2.17391304347826497e-02 7.93478260869565188e-01 2.17391304347826497e-02 8.04347826086956541e-01 1.08695652173913526e-02 8.15217391304347783e-01 1.08695652173913526e-02 8.26086956521739135e-01 1.08695652173913526e-02 8.36956521739130488e-01 1.08695652173913526e-02 8.47826086956521729e-01 1.08695652173913526e-02 8.58695652173913082e-01 1.08695652173913526e-02 8.69565217391304324e-01 1.08695652173913526e-02 9.02173913043478271e-01 1.08695652173913526e-02 9.13043478260869512e-01 1.08695652173913526e-02 9.23913043478260865e-01 1.08695652173913526e-02 9.34782608695652217e-01 1.08695652173913526e-02 9.45652173913043459e-01 1.08695652173913526e-02 9.56521739130434812e-01 1.08695652173913526e-02 9.67391304347826053e-01 1.08695652173913526e-02 9.78260869565217406e-01 1.08695652173913526e-02 9.89130434782608647e-01 1.08695652173913526e-02 1.00000000000000000e+00 10 14 2 25 Scale (current workspace) 8 15 87 1.00000000000000000e+00 1.55178066989507646e-01 1.00000000000000000e+00 1.55178066989507646e-01 9.97200363196125927e-01 1.55178066989507646e-01 9.92836965294592422e-01 1.55178066989507646e-01 9.86531476997578705e-01 1.55178066989507646e-01 9.77981234866828086e-01 1.55178066989507646e-01 9.63831719128329323e-01 1.55178066989507646e-01 9.21458837772397121e-01 1.53235976594027434e-01 8.70888821630346976e-01 1.50259786117836969e-01 8.20318805488297054e-01 1.47283595641646503e-01 6.54358353510895885e-01 1.48519471347861198e-01 5.77229620661824039e-01 1.52176654560129132e-01 4.92609967715899932e-01 1.56035613397901507e-01 3.23421105730427760e-01 1.59869350282485889e-01 2.31007869249394648e-01 1.59869350282485889e-01 1.53954802259887003e-01 1.59869350282485889e-01 9.08999192897497754e-02 1.59869350282485889e-01 6.97639225181598266e-02 1.59869350282485889e-01 6.12136803874092084e-02 1.59869350282485889e-01 5.49081920903954912e-02 1.59869350282485889e-01 5.21085552865213630e-02 1.59869350282485889e-01 4.93341404358353386e-02 1.59869350282485889e-01 4.76694915254237128e-02 1.60424233252623061e-01 4.54499596448749155e-02 1.62643765133171914e-01 4.32304277643260626e-02 1.69277138821630369e-01 3.82869249394673372e-02 2.03856436642453565e-01 3.33434221146085563e-02 2.38460956416464864e-01 2.75675948345439625e-02 3.33825161420500394e-01 2.75675948345439625e-02 3.78568906376109737e-01 2.75675948345439625e-02 4.48433716707021812e-01 2.75675948345439625e-02 5.88188559322033955e-01 2.49445117029862828e-02 6.27585250201775580e-01 2.30276432606941195e-02 6.48796912832929840e-01 2.10855528652139079e-02 6.70033797417271981e-01 1.93452380952380820e-02 6.87588276836158196e-01 1.75797013720742634e-02 7.05142756255044412e-01 1.35441888619854955e-02 7.51601594027441422e-01 9.30690072639223098e-03 7.77100988700564987e-01 7.18825665859562646e-03 8.02348163841807960e-01 5.09483454398707014e-03 8.27595338983050821e-01 1.86642453591606694e-03 8.42097962066182371e-01 9.83656174334157640e-04 8.46537025827280076e-01 2.52219531880537584e-04 8.49412328490718238e-01 0.00000000000000000e+00 8.49664548022598831e-01 0.00000000000000000e+00 8.50370762711864403e-01 0.00000000000000000e+00 8.51076977401129975e-01 5.54882970137227094e-04 8.52716404358353497e-01 7.31436642453564545e-04 8.52716404358353497e-01 1.13498789346244688e-03 8.51934523809523836e-01 2.01775625504441170e-03 8.47495460048426130e-01 2.01775625504441170e-03 8.41189971751412413e-01 2.01775625504441170e-03 8.36826573849878907e-01 2.01775625504441170e-03 8.30546307506053294e-01 2.01775625504441170e-03 8.26182909604519788e-01 2.01775625504441170e-03 8.17607445520581066e-01 2.01775625504441170e-03 8.03457929782082303e-01 2.01775625504441170e-03 7.86004338175948281e-01 2.01775625504441170e-03 7.60832828894269619e-01 4.64083938660209139e-03 7.21461359967715876e-01 9.73567393058916153e-03 6.47737590799031482e-01 1.26866424535915789e-02 5.97167574656981448e-01 1.26866424535915789e-02 5.62916162227602879e-01 1.51583938660209694e-02 4.99079398708635991e-01 1.70752623083131327e-02 4.77842514124293793e-01 1.88407990314770069e-02 4.60288034705407578e-01 2.04045601291363843e-02 4.46062853107344615e-01 2.04045601291363843e-02 4.31913337368845851e-01 2.04045601291363843e-02 4.17763821630347087e-01 2.04045601291363843e-02 4.03614305891848268e-01 2.04045601291363843e-02 3.82478309120258264e-01 2.04045601291363843e-02 3.61317090395480212e-01 2.04045601291363843e-02 3.29713983050847426e-01 2.04045601291363843e-02 3.18540657788539128e-01 2.04045601291363843e-02 3.12235169491525411e-01 2.04045601291363843e-02 2.92511602098466494e-01 2.16404358353510795e-02 2.83860472154963683e-01 2.28763115415657747e-02 2.75209342211460872e-01 2.39608555286521252e-02 2.68828188054882955e-01 2.50201775625504275e-02 2.62447033898305093e-01 2.50201775625504275e-02 2.56166767554479424e-01 2.50201775625504275e-02 2.47616525423728806e-01 2.50201775625504275e-02 2.41311037126715089e-01 2.50201775625504275e-02 2.32760794995964471e-01 2.50201775625504275e-02 2.24210552865213908e-01 2.50201775625504275e-02 2.17905064568200135e-01 2.50201775625504275e-02 2.09354822437449573e-01 2.50201775625504275e-02 2.04991424535916067e-01 10 16 3 22 Scale (all workspaces) 9 17 50 9.96427881830469264e-01 9.39225719250820656e-01 9.96427881830469264e-01 9.39225719250820656e-01 1.00000000000000000e+00 9.41639312608611778e-01 9.99565553195597589e-01 9.42122031280169914e-01 9.94014288472678142e-01 9.43473643560532871e-01 9.81994593550878481e-01 9.43473643560532871e-01 9.24985518439853216e-01 9.39804981656690464e-01 8.76665379416875834e-01 9.35750144815601481e-01 8.19994207375941309e-01 9.31405676771577484e-01 6.67551650897856730e-01 9.11179764433288297e-01 5.81289824290403545e-01 9.00415138057540032e-01 4.84022011971423027e-01 8.88926433674454453e-01 3.21007916586213526e-01 8.72803630044410195e-01 2.63854025873720754e-01 8.64018150222050529e-01 2.29774087661710757e-01 8.57211816953079797e-01 1.75275149642788175e-01 8.51129561691446268e-01 1.48194632168372253e-01 8.51129561691446268e-01 1.14742228229387899e-01 8.51129561691446268e-01 5.99053871403745775e-02 8.51129561691446268e-01 4.33964085730836335e-02 8.48764240200810982e-01 3.11836261826607397e-02 8.46736821780266435e-01 2.26395056960803087e-02 8.45047306429812739e-01 1.71365128403166422e-02 8.43647422282293835e-01 1.17783355860204830e-02 8.43647422282293835e-01 6.42015833172426831e-03 8.43647422282293835e-01 3.28248696659588779e-03 8.42585441204865782e-01 1.35161228036301262e-03 8.40702838385788742e-01 0.00000000000000000e+00 8.35199845530025131e-01 0.00000000000000000e+00 8.23180150608225469e-01 0.00000000000000000e+00 8.11112183819270083e-01 0.00000000000000000e+00 7.94748020853446535e-01 0.00000000000000000e+00 7.73363583703417623e-01 0.00000000000000000e+00 7.56951148870438351e-01 0.00000000000000000e+00 7.40586985904614803e-01 3.66866190384246282e-03 6.99942073759412975e-01 1.13921606487739080e-02 6.09673682178026644e-01 1.61228036300444244e-02 5.43975670978953496e-01 1.61228036300444244e-02 4.68768101950183425e-01 2.71770612087275376e-02 2.86203900366866160e-01 3.25352384630237523e-02 2.00473064298127057e-01 3.65418034369569544e-02 1.52152925275149620e-01 3.65418034369569544e-02 1.25072407800733754e-01 3.65418034369569544e-02 1.08659972967754370e-01 3.65418034369569544e-02 9.66402780459548194e-02 3.65418034369569544e-02 9.12821007916586047e-02 3.65418034369569544e-02 8.29310677737014612e-02 3.65418034369569544e-02 7.45800347557443732e-02 3.65418034369569544e-02 6.62290017377872298e-02 3.65418034369569544e-02 5.78779687198300863e-02 3.65418034369569544e-02 5.65263564394670737e-02 10 18 4 11 Open config 10 19 90 5.11363636363636354e-01 9.92424242424242431e-01 5.11363636363636354e-01 9.69696969696969724e-01 5.11363636363636354e-01 9.09090909090909061e-01 5.11363636363636354e-01 8.93939393939393923e-01 5.11363636363636354e-01 8.86363636363636354e-01 5.11363636363636354e-01 8.78787878787878785e-01 5.11363636363636354e-01 8.63636363636363646e-01 5.11363636363636354e-01 7.87878787878787845e-01 5.11363636363636354e-01 7.12121212121212155e-01 5.11363636363636354e-01 6.89393939393939448e-01 5.11363636363636354e-01 6.66666666666666630e-01 5.11363636363636354e-01 6.51515151515151492e-01 5.11363636363636354e-01 6.43939393939393923e-01 5.11363636363636354e-01 6.36363636363636354e-01 5.11363636363636354e-01 6.28787878787878785e-01 5.11363636363636354e-01 6.13636363636363646e-01 5.11363636363636354e-01 5.90909090909090939e-01 5.11363636363636354e-01 5.15151515151515138e-01 5.11363636363636354e-01 3.48484848484848508e-01 5.11363636363636354e-01 2.42424242424242431e-01 5.11363636363636354e-01 1.36363636363636354e-01 5.11363636363636354e-01 6.06060606060606077e-02 5.11363636363636354e-01 4.54545454545454697e-02 5.11363636363636354e-01 3.03030303030302761e-02 5.11363636363636354e-01 2.27272727272727071e-02 5.11363636363636354e-01 1.51515151515151381e-02 5.11363636363636354e-01 7.57575757575756903e-03 5.11363636363636354e-01 0.00000000000000000e+00 5.11363636363636354e-01 7.57575757575756903e-03 5.11363636363636354e-01 1.51515151515151381e-02 5.11363636363636354e-01 3.03030303030302761e-02 5.26515151515151492e-01 9.09090909090909394e-02 5.26515151515151492e-01 1.13636363636363646e-01 5.26515151515151492e-01 1.74242424242424254e-01 5.26515151515151492e-01 1.96969696969696961e-01 5.26515151515151492e-01 2.12121212121212155e-01 5.26515151515151492e-01 2.19696969696969724e-01 5.26515151515151492e-01 2.34848484848484862e-01 5.26515151515151492e-01 2.42424242424242431e-01 5.26515151515151492e-01 3.03030303030303039e-01 5.26515151515151492e-01 3.25757575757575746e-01 5.26515151515151492e-01 3.48484848484848508e-01 5.26515151515151492e-01 3.56060606060606077e-01 5.26515151515151492e-01 3.71212121212121215e-01 5.26515151515151492e-01 3.78787878787878785e-01 5.26515151515151492e-01 4.39393939393939392e-01 5.26515151515151492e-01 4.62121212121212099e-01 5.26515151515151492e-01 4.84848484848484862e-01 5.26515151515151492e-01 5.07575757575757569e-01 5.11363636363636354e-01 5.68181818181818232e-01 5.11363636363636354e-01 5.83333333333333370e-01 5.11363636363636354e-01 5.98484848484848508e-01 5.03787878787878785e-01 5.98484848484848508e-01 5.03787878787878785e-01 6.06060606060606077e-01 5.03787878787878785e-01 6.13636363636363646e-01 5.03787878787878785e-01 6.21212121212121215e-01 5.03787878787878785e-01 6.36363636363636354e-01 4.96212121212121215e-01 6.43939393939393923e-01 4.96212121212121215e-01 6.59090909090909061e-01 4.96212121212121215e-01 6.66666666666666630e-01 4.81060606060606077e-01 7.12121212121212155e-01 4.81060606060606077e-01 7.19696969696969724e-01 4.81060606060606077e-01 7.42424242424242431e-01 4.81060606060606077e-01 7.50000000000000000e-01 4.81060606060606077e-01 7.65151515151515138e-01 4.81060606060606077e-01 7.80303030303030276e-01 4.81060606060606077e-01 7.95454545454545414e-01 4.81060606060606077e-01 8.03030303030302983e-01 4.81060606060606077e-01 8.10606060606060552e-01 4.73484848484848508e-01 8.10606060606060552e-01 4.73484848484848508e-01 8.18181818181818232e-01 4.73484848484848508e-01 8.25757575757575801e-01 4.73484848484848508e-01 8.33333333333333370e-01 4.73484848484848508e-01 8.40909090909090939e-01 4.73484848484848508e-01 8.48484848484848508e-01 4.73484848484848508e-01 8.56060606060606077e-01 4.73484848484848508e-01 8.63636363636363646e-01 4.73484848484848508e-01 8.71212121212121215e-01 4.73484848484848508e-01 8.78787878787878785e-01 4.88636363636363646e-01 9.24242424242424199e-01 4.88636363636363646e-01 9.39393939393939448e-01 4.88636363636363646e-01 9.46969696969697017e-01 4.96212121212121215e-01 9.54545454545454586e-01 5.03787878787878785e-01 9.62121212121212155e-01 5.03787878787878785e-01 9.69696969696969724e-01 5.03787878787878785e-01 9.77272727272727293e-01 5.03787878787878785e-01 9.84848484848484862e-01 5.03787878787878785e-01 9.92424242424242431e-01 5.03787878787878785e-01 1.00000000000000000e+00 5.11363636363636354e-01 1.00000000000000000e+00 11 7 SendKey 1 2 20 0 1 0 71 7 Refresh 11 21 52 1.00000000000000000e+00 4.58762886597938180e-01 1.00000000000000000e+00 4.58762886597938180e-01 9.89690721649484573e-01 4.58762886597938180e-01 9.79381443298969034e-01 4.58762886597938180e-01 9.17525773195876249e-01 4.58762886597938180e-01 8.96907216494845394e-01 4.58762886597938180e-01 8.76288659793814428e-01 4.58762886597938180e-01 8.65979381443299001e-01 4.58762886597938180e-01 8.35051546391752608e-01 4.69072164948453607e-01 8.24742268041237070e-01 4.69072164948453607e-01 8.14432989690721643e-01 4.69072164948453607e-01 8.04123711340206215e-01 4.69072164948453607e-01 7.93814432989690677e-01 4.69072164948453607e-01 7.62886597938144284e-01 4.79381443298969145e-01 7.21649484536082464e-01 4.79381443298969145e-01 6.90721649484536071e-01 4.79381443298969145e-01 6.70103092783505105e-01 5.00000000000000000e-01 6.28865979381443285e-01 5.00000000000000000e-01 5.46391752577319534e-01 5.30927835051546393e-01 4.84536082474226804e-01 5.30927835051546393e-01 4.53608247422680411e-01 5.30927835051546393e-01 4.43298969072164928e-01 5.30927835051546393e-01 4.43298969072164928e-01 5.30927835051546393e-01 4.22680412371134018e-01 5.41237113402061820e-01 4.12371134020618535e-01 5.41237113402061820e-01 4.02061855670103108e-01 5.41237113402061820e-01 3.91752577319587625e-01 5.41237113402061820e-01 3.81443298969072142e-01 5.41237113402061820e-01 3.71134020618556715e-01 5.41237113402061820e-01 3.60824742268041232e-01 5.41237113402061820e-01 3.29896907216494839e-01 5.41237113402061820e-01 2.88659793814432963e-01 5.41237113402061820e-01 2.78350515463917536e-01 5.41237113402061820e-01 2.57731958762886570e-01 5.41237113402061820e-01 2.37113402061855660e-01 5.41237113402061820e-01 2.06185567010309267e-01 5.41237113402061820e-01 1.75257731958762875e-01 5.41237113402061820e-01 1.64948453608247392e-01 5.41237113402061820e-01 1.64948453608247392e-01 5.41237113402061820e-01 1.44329896907216482e-01 5.41237113402061820e-01 1.34020618556700999e-01 5.41237113402061820e-01 1.13402061855670089e-01 5.41237113402061820e-01 1.03092783505154606e-01 5.41237113402061820e-01 8.24742268041236959e-02 5.41237113402061820e-01 7.21649484536082131e-02 5.41237113402061820e-01 6.18556701030927858e-02 5.30927835051546393e-01 5.15463917525773030e-02 5.30927835051546393e-01 4.12371134020618757e-02 5.30927835051546393e-01 3.09278350515463929e-02 5.30927835051546393e-01 2.06185567010309101e-02 5.30927835051546393e-01 1.03092783505154828e-02 5.30927835051546393e-01 0.00000000000000000e+00 5.30927835051546393e-01 11 22 8 113 4 Back 12 23 31 0.00000000000000000e+00 5.08130081300813052e-01 8.13008130081300795e-02 4.91869918699186948e-01 1.05691056910569126e-01 4.91869918699186948e-01 1.70731707317073211e-01 4.91869918699186948e-01 2.35772357723577242e-01 4.91869918699186948e-01 3.33333333333333315e-01 4.91869918699186948e-01 3.98373983739837401e-01 4.91869918699186948e-01 5.12195121951219523e-01 4.91869918699186948e-01 5.93495934959349603e-01 4.91869918699186948e-01 7.07317073170731669e-01 4.91869918699186948e-01 7.72357723577235755e-01 4.91869918699186948e-01 7.96747967479674801e-01 4.91869918699186948e-01 8.13008130081300795e-01 4.91869918699186948e-01 8.29268292682926789e-01 4.91869918699186948e-01 8.37398373983739841e-01 4.91869918699186948e-01 8.45528455284552893e-01 4.91869918699186948e-01 8.53658536585365835e-01 4.91869918699186948e-01 8.69918699186991828e-01 4.91869918699186948e-01 8.78048780487804881e-01 4.91869918699186948e-01 8.86178861788617933e-01 4.91869918699186948e-01 8.94308943089430874e-01 4.91869918699186948e-01 9.02439024390243927e-01 4.91869918699186948e-01 9.10569105691056868e-01 4.91869918699186948e-01 9.18699186991869921e-01 4.91869918699186948e-01 9.34959349593495914e-01 4.91869918699186948e-01 9.59349593495934960e-01 4.91869918699186948e-01 9.67479674796748013e-01 4.91869918699186948e-01 9.75609756097560954e-01 4.91869918699186948e-01 9.83739837398374006e-01 4.91869918699186948e-01 9.91869918699186948e-01 4.91869918699186948e-01 1.00000000000000000e+00 4.91869918699186948e-01 11 24 8 114 7 Forward 13 25 30 4.70873786407767003e-01 1.00000000000000000e+00 4.70873786407767003e-01 9.90291262135922334e-01 4.70873786407767003e-01 9.12621359223301010e-01 4.70873786407767003e-01 8.83495145631068013e-01 4.70873786407767003e-01 8.64077669902912571e-01 4.70873786407767003e-01 8.44660194174757240e-01 4.70873786407767003e-01 8.34951456310679574e-01 4.70873786407767003e-01 8.25242718446601908e-01 4.70873786407767003e-01 7.47572815533980584e-01 4.80582524271844669e-01 7.28155339805825252e-01 4.80582524271844669e-01 6.11650485436893154e-01 4.80582524271844669e-01 5.33980582524271830e-01 4.80582524271844669e-01 4.36893203883495118e-01 4.90291262135922334e-01 4.17475728155339787e-01 4.90291262135922334e-01 3.98058252427184456e-01 4.90291262135922334e-01 3.88349514563106790e-01 4.90291262135922334e-01 3.78640776699029125e-01 4.90291262135922334e-01 3.68932038834951459e-01 4.90291262135922334e-01 3.49514563106796128e-01 4.90291262135922334e-01 3.30097087378640797e-01 5.09708737864077666e-01 2.33009708737864085e-01 5.09708737864077666e-01 1.55339805825242705e-01 5.29126213592232997e-01 7.76699029126213247e-02 5.29126213592232997e-01 5.82524271844659935e-02 5.29126213592232997e-01 4.85436893203883280e-02 5.29126213592232997e-01 3.88349514563106624e-02 5.29126213592232997e-01 2.91262135922329968e-02 5.29126213592232997e-01 1.94174757281553312e-02 5.29126213592232997e-01 9.70873786407766559e-03 5.29126213592232997e-01 0.00000000000000000e+00 11 26 0 22 2 Up 14 27 49 0.00000000000000000e+00 8.82664802938238902e-01 0.00000000000000000e+00 8.82664802938238902e-01 1.85187945698050971e-04 8.82479614992540906e-01 1.85187945698050971e-04 8.81955584706571649e-01 7.25271675876382194e-04 8.79794963117211948e-01 1.39320962243444368e-03 8.76454986715774931e-01 1.39320962243444368e-03 8.57459462186685561e-01 1.39320962243444368e-03 8.41608119373411068e-01 1.39320962243444368e-03 8.22743889084460611e-01 1.39320962243444368e-03 7.71128625928376388e-01 1.39320962243444368e-03 7.23836612631537868e-01 1.39320962243444368e-03 6.19034282130108582e-01 1.39320962243444368e-03 5.43576787637013226e-01 1.39320962243444368e-03 4.91175765720621937e-01 1.39320962243444368e-03 4.38774457135583940e-01 1.39320962243444368e-03 3.75369085882486520e-01 1.39320962243444368e-03 3.32924066062216251e-01 3.62463836750231971e-03 2.94998950792753512e-01 5.98621467812271435e-03 2.52488570521060440e-01 7.95562828020596191e-03 2.22947366489811227e-01 9.79432097944354929e-03 1.97205382031838572e-01 1.16330136786811367e-02 1.71463397573865917e-01 1.29493961038291139e-02 1.58297853310506886e-01 1.40054833978719984e-02 1.49848581620870391e-01 1.56624281751706063e-02 1.44877173951681426e-01 1.70734112536937155e-02 1.41350002924020168e-01 1.93650404145457866e-02 1.37530429876835669e-01 2.27207835915453349e-02 1.33336467577202744e-01 2.73522022458768665e-02 1.28704762254224614e-01 3.11717752930613656e-02 1.26413133093372543e-01 4.28185490667788993e-02 1.22848695141654418e-01 5.12683940937085358e-02 1.21792607847611534e-01 7.70100918830345926e-02 1.19953628479727348e-01 1.48537359231361077e-01 1.20084636051219662e-01 2.24060787513172432e-01 1.23231397784854035e-01 2.81898480312170685e-01 1.25985423472658120e-01 4.14669637318295092e-01 1.28870456731954519e-01 4.52529392136335029e-01 1.28870456731954519e-01 5.16000697178148560e-01 1.25985423472658120e-01 6.26305059013607557e-01 1.20608092999895666e-01 7.08126024123739928e-01 1.17335197061761043e-01 7.60526759371484729e-01 1.17335197061761043e-01 8.40831820678441533e-01 1.17335197061761043e-01 8.70307664258267888e-01 1.17335197061761043e-01 8.99782934500800935e-01 1.17335197061761043e-01 9.42228240989717802e-01 1.17335197061761043e-01 9.75764459279865437e-01 1.17335197061761043e-01 9.91616088761786418e-01 1.17335197061761043e-01 1.00000000000000000e+00 1.17335197061761043e-01 11 28 4 117 8 Next Tab 15 29 75 8.30357142857142905e-01 1.00000000000000000e+00 8.39285714285714302e-01 9.82142857142857095e-01 8.39285714285714302e-01 9.73214285714285698e-01 8.57142857142857095e-01 8.30357142857142905e-01 8.57142857142857095e-01 7.58928571428571397e-01 8.57142857142857095e-01 6.87500000000000000e-01 8.57142857142857095e-01 6.69642857142857095e-01 8.57142857142857095e-01 6.60714285714285698e-01 8.57142857142857095e-01 6.51785714285714302e-01 8.57142857142857095e-01 6.42857142857142905e-01 8.57142857142857095e-01 6.33928571428571397e-01 8.57142857142857095e-01 6.16071428571428603e-01 8.57142857142857095e-01 5.98214285714285698e-01 8.57142857142857095e-01 5.71428571428571397e-01 8.57142857142857095e-01 5.53571428571428603e-01 8.57142857142857095e-01 5.44642857142857095e-01 8.57142857142857095e-01 5.26785714285714302e-01 8.57142857142857095e-01 5.17857142857142905e-01 8.57142857142857095e-01 4.28571428571428548e-01 8.57142857142857095e-01 3.57142857142857151e-01 8.57142857142857095e-01 2.67857142857142849e-01 8.57142857142857095e-01 2.41071428571428603e-01 8.57142857142857095e-01 2.23214285714285698e-01 8.57142857142857095e-01 2.05357142857142849e-01 8.57142857142857095e-01 1.96428571428571397e-01 8.57142857142857095e-01 1.87500000000000000e-01 8.57142857142857095e-01 1.78571428571428603e-01 8.66071428571428603e-01 1.69642857142857151e-01 8.66071428571428603e-01 1.60714285714285698e-01 8.66071428571428603e-01 1.51785714285714302e-01 8.66071428571428603e-01 1.33928571428571397e-01 8.66071428571428603e-01 1.25000000000000000e-01 8.66071428571428603e-01 1.16071428571428603e-01 8.66071428571428603e-01 1.07142857142857151e-01 8.66071428571428603e-01 9.82142857142856984e-02 8.75000000000000000e-01 9.82142857142856984e-02 8.75000000000000000e-01 8.92857142857143016e-02 8.75000000000000000e-01 6.25000000000000000e-02 8.75000000000000000e-01 5.35714285714285476e-02 8.75000000000000000e-01 3.57142857142856984e-02 8.75000000000000000e-01 2.67857142857143016e-02 8.75000000000000000e-01 1.78571428571428492e-02 8.75000000000000000e-01 8.92857142857145236e-03 8.75000000000000000e-01 0.00000000000000000e+00 8.66071428571428603e-01 0.00000000000000000e+00 8.57142857142857095e-01 0.00000000000000000e+00 8.48214285714285698e-01 0.00000000000000000e+00 8.39285714285714302e-01 0.00000000000000000e+00 8.21428571428571397e-01 0.00000000000000000e+00 8.12500000000000000e-01 0.00000000000000000e+00 8.03571428571428603e-01 0.00000000000000000e+00 7.85714285714285698e-01 0.00000000000000000e+00 7.76785714285714302e-01 0.00000000000000000e+00 7.50000000000000000e-01 0.00000000000000000e+00 7.32142857142857095e-01 0.00000000000000000e+00 6.60714285714285698e-01 1.78571428571428492e-02 6.33928571428571397e-01 1.78571428571428492e-02 5.44642857142857095e-01 5.35714285714285476e-02 4.73214285714285698e-01 5.35714285714285476e-02 3.83928571428571452e-01 7.14285714285713969e-02 3.66071428571428548e-01 7.14285714285713969e-02 3.48214285714285698e-01 7.14285714285713969e-02 3.39285714285714302e-01 7.14285714285713969e-02 3.21428571428571452e-01 7.14285714285713969e-02 2.94642857142857151e-01 7.14285714285713969e-02 2.41071428571428603e-01 5.35714285714285476e-02 2.14285714285714302e-01 5.35714285714285476e-02 1.96428571428571397e-01 5.35714285714285476e-02 1.69642857142857151e-01 5.35714285714285476e-02 1.60714285714285698e-01 5.35714285714285476e-02 1.51785714285714302e-01 5.35714285714285476e-02 1.42857142857142849e-01 5.35714285714285476e-02 1.33928571428571397e-01 5.35714285714285476e-02 1.33928571428571397e-01 4.46428571428571508e-02 1.25000000000000000e-01 4.46428571428571508e-02 11 30 4 112 12 Previous Tab 16 31 53 0.00000000000000000e+00 1.13924050632911444e-01 0.00000000000000000e+00 2.02531645569620278e-01 0.00000000000000000e+00 2.53164556962025333e-01 0.00000000000000000e+00 3.03797468354430444e-01 0.00000000000000000e+00 3.67088607594936778e-01 0.00000000000000000e+00 4.43037974683544333e-01 0.00000000000000000e+00 4.93670886075949444e-01 0.00000000000000000e+00 5.94936708860759444e-01 0.00000000000000000e+00 6.58227848101265778e-01 0.00000000000000000e+00 7.21518987341772111e-01 0.00000000000000000e+00 7.40506329113924000e-01 0.00000000000000000e+00 7.91139240506329111e-01 0.00000000000000000e+00 7.97468354430379778e-01 0.00000000000000000e+00 8.03797468354430333e-01 0.00000000000000000e+00 8.10126582278481000e-01 0.00000000000000000e+00 8.16455696202531667e-01 0.00000000000000000e+00 8.22784810126582333e-01 0.00000000000000000e+00 8.29113924050632889e-01 0.00000000000000000e+00 8.35443037974683556e-01 0.00000000000000000e+00 8.41772151898734222e-01 0.00000000000000000e+00 8.48101265822784778e-01 0.00000000000000000e+00 8.54430379746835444e-01 0.00000000000000000e+00 8.60759493670886111e-01 0.00000000000000000e+00 8.67088607594936667e-01 0.00000000000000000e+00 8.73417721518987333e-01 0.00000000000000000e+00 8.79746835443038000e-01 0.00000000000000000e+00 8.86075949367088556e-01 1.26582278481012778e-02 8.79746835443038000e-01 2.53164556962025555e-02 8.79746835443038000e-01 8.86075949367088889e-02 8.79746835443038000e-01 1.39240506329113889e-01 8.67088607594936667e-01 2.15189873417721556e-01 8.54430379746835444e-01 2.53164556962025333e-01 8.41772151898734222e-01 3.16455696202531667e-01 8.29113924050632889e-01 3.54430379746835444e-01 8.16455696202531667e-01 4.05063291139240500e-01 7.91139240506329111e-01 4.81012658227848111e-01 7.78481012658227889e-01 5.44303797468354444e-01 7.65822784810126556e-01 6.45569620253164556e-01 7.65822784810126556e-01 7.21518987341772111e-01 7.53164556962025333e-01 8.10126582278481000e-01 7.53164556962025333e-01 8.73417721518987333e-01 7.53164556962025333e-01 8.92405063291139222e-01 7.53164556962025333e-01 9.11392405063291111e-01 7.53164556962025333e-01 9.30379746835443000e-01 7.53164556962025333e-01 9.36708860759493667e-01 7.53164556962025333e-01 9.55696202531645556e-01 7.53164556962025333e-01 9.62025316455696222e-01 7.53164556962025333e-01 9.74683544303797444e-01 7.53164556962025333e-01 9.81012658227848111e-01 7.53164556962025333e-01 9.87341772151898778e-01 7.53164556962025333e-01 9.93670886075949333e-01 7.53164556962025333e-01 1.00000000000000000e+00 7.53164556962025333e-01 11 32 4 25 9 Close Tab 17 33 117 5.00000000000000000e-01 0.00000000000000000e+00 5.00000000000000000e-01 1.53846153846153855e-02 5.00000000000000000e-01 2.30769230769231060e-02 5.00000000000000000e-01 4.61538461538461564e-02 5.15384615384615330e-01 1.07692307692307698e-01 5.15384615384615330e-01 1.30769230769230749e-01 5.15384615384615330e-01 1.38461538461538469e-01 5.15384615384615330e-01 1.61538461538461520e-01 5.15384615384615330e-01 1.69230769230769240e-01 5.15384615384615330e-01 2.30769230769230782e-01 5.15384615384615330e-01 2.46153846153846168e-01 5.15384615384615330e-01 3.07692307692307709e-01 5.15384615384615330e-01 3.23076923076923095e-01 5.15384615384615330e-01 3.38461538461538480e-01 5.15384615384615330e-01 3.46153846153846145e-01 5.15384615384615330e-01 3.53846153846153866e-01 5.15384615384615330e-01 3.69230769230769251e-01 5.15384615384615330e-01 4.61538461538461564e-01 5.15384615384615330e-01 5.38461538461538436e-01 5.15384615384615330e-01 6.15384615384615419e-01 5.15384615384615330e-01 6.38461538461538414e-01 5.15384615384615330e-01 6.61538461538461520e-01 5.15384615384615330e-01 6.69230769230769185e-01 5.15384615384615330e-01 6.76923076923076961e-01 5.15384615384615330e-01 6.92307692307692291e-01 5.15384615384615330e-01 6.99999999999999956e-01 5.15384615384615330e-01 7.23076923076923062e-01 5.15384615384615330e-01 7.38461538461538503e-01 5.15384615384615330e-01 8.00000000000000044e-01 5.15384615384615330e-01 8.23076923076923039e-01 5.15384615384615330e-01 9.00000000000000022e-01 5.15384615384615330e-01 9.15384615384615352e-01 5.15384615384615330e-01 9.38461538461538458e-01 5.15384615384615330e-01 9.53846153846153899e-01 5.15384615384615330e-01 9.69230769230769229e-01 5.15384615384615330e-01 9.76923076923076894e-01 5.15384615384615330e-01 9.84615384615384670e-01 5.15384615384615330e-01 9.92307692307692335e-01 5.15384615384615330e-01 1.00000000000000000e+00 5.07692307692307665e-01 1.00000000000000000e+00 5.07692307692307665e-01 9.92307692307692335e-01 5.07692307692307665e-01 9.84615384615384670e-01 5.00000000000000000e-01 9.69230769230769229e-01 5.00000000000000000e-01 9.53846153846153899e-01 4.84615384615384670e-01 8.92307692307692357e-01 4.84615384615384670e-01 8.76923076923076916e-01 4.84615384615384670e-01 8.53846153846153810e-01 4.84615384615384670e-01 8.46153846153846145e-01 4.84615384615384670e-01 8.38461538461538480e-01 4.84615384615384670e-01 8.30769230769230815e-01 4.84615384615384670e-01 8.15384615384615374e-01 4.84615384615384670e-01 8.07692307692307709e-01 4.84615384615384670e-01 7.46153846153846168e-01 4.84615384615384670e-01 7.30769230769230727e-01 4.84615384615384670e-01 7.07692307692307732e-01 4.84615384615384670e-01 6.99999999999999956e-01 4.84615384615384670e-01 6.92307692307692291e-01 4.84615384615384670e-01 6.76923076923076961e-01 4.84615384615384670e-01 6.61538461538461520e-01 4.84615384615384670e-01 6.46153846153846190e-01 4.84615384615384670e-01 6.30769230769230749e-01 5.00000000000000000e-01 5.69230769230769207e-01 5.00000000000000000e-01 5.61538461538461542e-01 5.00000000000000000e-01 5.46153846153846101e-01 5.07692307692307665e-01 5.38461538461538436e-01 5.07692307692307665e-01 5.30769230769230771e-01 5.07692307692307665e-01 5.23076923076923106e-01 5.07692307692307665e-01 5.15384615384615330e-01 5.07692307692307665e-01 5.07692307692307665e-01 5.07692307692307665e-01 5.00000000000000000e-01 5.07692307692307665e-01 4.92307692307692335e-01 5.07692307692307665e-01 4.84615384615384615e-01 5.07692307692307665e-01 4.61538461538461564e-01 5.07692307692307665e-01 4.53846153846153844e-01 5.07692307692307665e-01 4.38461538461538458e-01 5.07692307692307665e-01 4.30769230769230793e-01 5.07692307692307665e-01 4.15384615384615408e-01 5.07692307692307665e-01 4.07692307692307687e-01 5.07692307692307665e-01 3.84615384615384637e-01 5.07692307692307665e-01 3.69230769230769251e-01 5.07692307692307665e-01 3.61538461538461531e-01 5.07692307692307665e-01 3.53846153846153866e-01 5.07692307692307665e-01 3.38461538461538480e-01 5.07692307692307665e-01 3.30769230769230760e-01 5.07692307692307665e-01 3.23076923076923095e-01 5.00000000000000000e-01 3.15384615384615374e-01 5.00000000000000000e-01 3.07692307692307709e-01 5.00000000000000000e-01 2.92307692307692324e-01 5.00000000000000000e-01 2.84615384615384603e-01 5.00000000000000000e-01 2.76923076923076938e-01 5.00000000000000000e-01 2.69230769230769218e-01 5.00000000000000000e-01 2.53846153846153832e-01 5.00000000000000000e-01 2.38461538461538503e-01 4.92307692307692335e-01 2.23076923076923062e-01 4.92307692307692335e-01 2.15384615384615397e-01 4.92307692307692335e-01 2.00000000000000011e-01 4.92307692307692335e-01 1.92307692307692291e-01 4.92307692307692335e-01 1.84615384615384626e-01 4.92307692307692335e-01 1.76923076923076961e-01 4.92307692307692335e-01 1.61538461538461520e-01 4.92307692307692335e-01 1.53846153846153855e-01 4.92307692307692335e-01 1.46153846153846190e-01 4.92307692307692335e-01 1.38461538461538469e-01 4.92307692307692335e-01 1.30769230769230749e-01 4.92307692307692335e-01 1.23076923076923084e-01 4.92307692307692335e-01 1.07692307692307698e-01 4.92307692307692335e-01 9.99999999999999778e-02 4.92307692307692335e-01 8.46153846153846478e-02 4.92307692307692335e-01 7.69230769230769273e-02 4.92307692307692335e-01 6.92307692307692069e-02 4.92307692307692335e-01 6.15384615384615419e-02 4.92307692307692335e-01 5.38461538461538769e-02 4.92307692307692335e-01 4.61538461538461564e-02 4.92307692307692335e-01 3.84615384615384359e-02 4.92307692307692335e-01 3.07692307692307709e-02 4.92307692307692335e-01 2.30769230769231060e-02 4.92307692307692335e-01 1.53846153846153855e-02 11 34 4 28 7 New Tab 18 35 87 0.00000000000000000e+00 4.09836065573770947e-02 0.00000000000000000e+00 5.73770491803279326e-02 0.00000000000000000e+00 7.37704918032787704e-02 0.00000000000000000e+00 9.01639344262295528e-02 0.00000000000000000e+00 1.55737704918032849e-01 0.00000000000000000e+00 2.37704918032786927e-01 0.00000000000000000e+00 2.54098360655737709e-01 0.00000000000000000e+00 2.86885245901639330e-01 0.00000000000000000e+00 3.03278688524590168e-01 0.00000000000000000e+00 3.52459016393442626e-01 0.00000000000000000e+00 4.01639344262295084e-01 0.00000000000000000e+00 4.18032786885245922e-01 0.00000000000000000e+00 4.50819672131147542e-01 0.00000000000000000e+00 5.00000000000000000e-01 0.00000000000000000e+00 5.49180327868852514e-01 0.00000000000000000e+00 6.31147540983606592e-01 0.00000000000000000e+00 6.96721311475409832e-01 0.00000000000000000e+00 7.62295081967213073e-01 0.00000000000000000e+00 8.44262295081967262e-01 0.00000000000000000e+00 8.60655737704917989e-01 0.00000000000000000e+00 8.77049180327868827e-01 0.00000000000000000e+00 8.93442622950819665e-01 0.00000000000000000e+00 9.09836065573770503e-01 0.00000000000000000e+00 9.42622950819672178e-01 0.00000000000000000e+00 9.59016393442622905e-01 1.63934426229508379e-02 9.59016393442622905e-01 3.27868852459016202e-02 9.59016393442622905e-01 4.91803278688524581e-02 9.59016393442622905e-01 6.55737704918032960e-02 9.59016393442622905e-01 9.83606557377049162e-02 9.59016393442622905e-01 1.47540983606557374e-01 9.59016393442622905e-01 1.63934426229508157e-01 9.59016393442622905e-01 1.80327868852458995e-01 9.59016393442622905e-01 1.96721311475409832e-01 9.59016393442622905e-01 2.13114754098360670e-01 9.59016393442622905e-01 2.29508196721311508e-01 9.59016393442622905e-01 2.45901639344262291e-01 9.59016393442622905e-01 2.62295081967213128e-01 9.42622950819672178e-01 2.78688524590163911e-01 9.42622950819672178e-01 3.44262295081967207e-01 9.42622950819672178e-01 4.09836065573770503e-01 9.42622950819672178e-01 4.26229508196721285e-01 9.42622950819672178e-01 4.75409836065573743e-01 9.42622950819672178e-01 4.75409836065573743e-01 9.09836065573770503e-01 4.91803278688524581e-01 9.09836065573770503e-01 5.08196721311475419e-01 9.09836065573770503e-01 5.73770491803278659e-01 9.09836065573770503e-01 6.55737704918032738e-01 9.09836065573770503e-01 6.72131147540983576e-01 9.09836065573770503e-01 6.72131147540983576e-01 8.93442622950819665e-01 6.88524590163934413e-01 8.93442622950819665e-01 7.04918032786885251e-01 8.93442622950819665e-01 7.21311475409836089e-01 8.93442622950819665e-01 7.54098360655737654e-01 9.09836065573770503e-01 7.70491803278688492e-01 9.26229508196721341e-01 8.36065573770491843e-01 9.26229508196721341e-01 8.85245901639344246e-01 9.26229508196721341e-01 9.01639344262295084e-01 9.26229508196721341e-01 9.18032786885245922e-01 9.42622950819672178e-01 9.50819672131147486e-01 9.42622950819672178e-01 9.50819672131147486e-01 9.09836065573770503e-01 9.50819672131147486e-01 8.93442622950819665e-01 9.50819672131147486e-01 8.60655737704917989e-01 1.00000000000000000e+00 7.95081967213114749e-01 1.00000000000000000e+00 7.62295081967213073e-01 1.00000000000000000e+00 7.45901639344262346e-01 1.00000000000000000e+00 7.29508196721311508e-01 1.00000000000000000e+00 6.96721311475409832e-01 1.00000000000000000e+00 6.63934426229508157e-01 1.00000000000000000e+00 6.31147540983606592e-01 1.00000000000000000e+00 5.81967213114754078e-01 1.00000000000000000e+00 5.16393442622950838e-01 1.00000000000000000e+00 4.67213114754098380e-01 1.00000000000000000e+00 4.50819672131147542e-01 1.00000000000000000e+00 4.34426229508196704e-01 1.00000000000000000e+00 4.18032786885245922e-01 1.00000000000000000e+00 4.01639344262295084e-01 1.00000000000000000e+00 3.68852459016393464e-01 1.00000000000000000e+00 3.52459016393442626e-01 1.00000000000000000e+00 3.36065573770491788e-01 1.00000000000000000e+00 3.19672131147541005e-01 1.00000000000000000e+00 3.03278688524590168e-01 1.00000000000000000e+00 2.86885245901639330e-01 1.00000000000000000e+00 2.70491803278688547e-01 1.00000000000000000e+00 2.54098360655737709e-01 1.00000000000000000e+00 1.88524590163934469e-01 1.00000000000000000e+00 1.39344262295082011e-01 11 36 4 32 9 Open File 19 37 119 2.54437869822485285e-01 0.00000000000000000e+00 2.54437869822485285e-01 5.91715976331358418e-03 2.54437869822485285e-01 1.18343195266272239e-02 2.54437869822485285e-01 1.77514792899408080e-02 2.54437869822485285e-01 3.55029585798816716e-02 2.54437869822485285e-01 5.32544378698224796e-02 2.54437869822485285e-01 5.91715976331360638e-02 2.54437869822485285e-01 6.50887573964497035e-02 2.54437869822485285e-01 7.10059171597633432e-02 2.54437869822485285e-01 7.69230769230769273e-02 2.54437869822485285e-01 1.06508875739644959e-01 2.54437869822485285e-01 1.30177514792899407e-01 2.54437869822485285e-01 1.53846153846153855e-01 2.54437869822485285e-01 1.83431952662721887e-01 2.54437869822485285e-01 1.95266272189349110e-01 2.54437869822485285e-01 2.13017751479289918e-01 2.54437869822485285e-01 2.18934911242603558e-01 2.54437869822485285e-01 2.24852071005917198e-01 2.54437869822485285e-01 2.36686390532544366e-01 2.54437869822485285e-01 2.42603550295858006e-01 2.54437869822485285e-01 2.48520710059171590e-01 2.54437869822485285e-01 2.54437869822485230e-01 2.54437869822485285e-01 2.72189349112426038e-01 2.54437869822485285e-01 2.89940828402366846e-01 2.54437869822485285e-01 2.95857988165680486e-01 2.54437869822485285e-01 3.01775147928994070e-01 2.54437869822485285e-01 3.07692307692307709e-01 2.54437869822485285e-01 3.25443786982248517e-01 2.54437869822485285e-01 3.37278106508875741e-01 2.48520710059171646e-01 3.43195266272189325e-01 2.48520710059171646e-01 3.49112426035502965e-01 2.48520710059171646e-01 3.78698224852070997e-01 2.48520710059171646e-01 4.26035502958579892e-01 2.48520710059171646e-01 4.91124260355029596e-01 2.72189349112426093e-01 5.20710059171597628e-01 2.72189349112426093e-01 5.32544378698224907e-01 2.72189349112426093e-01 5.44378698224852076e-01 2.72189349112426093e-01 5.50295857988165715e-01 2.78106508875739622e-01 5.50295857988165715e-01 2.84023668639053262e-01 5.50295857988165715e-01 3.01775147928994070e-01 5.50295857988165715e-01 3.13609467455621349e-01 5.50295857988165715e-01 3.19526627218934989e-01 5.50295857988165715e-01 3.25443786982248517e-01 5.50295857988165715e-01 3.31360946745562157e-01 5.50295857988165715e-01 3.37278106508875797e-01 5.50295857988165715e-01 3.55029585798816605e-01 5.50295857988165715e-01 3.66863905325443773e-01 5.50295857988165715e-01 3.78698224852071053e-01 5.50295857988165715e-01 3.96449704142011861e-01 5.50295857988165715e-01 4.02366863905325500e-01 5.50295857988165715e-01 4.08284023668639029e-01 5.50295857988165715e-01 4.26035502958579948e-01 5.50295857988165715e-01 4.43786982248520756e-01 5.50295857988165715e-01 4.55621301775147924e-01 5.50295857988165715e-01 4.61538461538461564e-01 5.50295857988165715e-01 4.67455621301775204e-01 5.50295857988165715e-01 4.73372781065088732e-01 5.50295857988165715e-01 4.91124260355029651e-01 5.50295857988165715e-01 5.02958579881656820e-01 5.50295857988165715e-01 5.08875739644970460e-01 5.50295857988165715e-01 5.32544378698224907e-01 5.50295857988165715e-01 5.50295857988165715e-01 5.50295857988165715e-01 5.56213017751479244e-01 5.50295857988165715e-01 5.79881656804733692e-01 5.50295857988165715e-01 6.09467455621301779e-01 5.50295857988165715e-01 6.27218934911242587e-01 5.50295857988165715e-01 6.44970414201183395e-01 5.50295857988165715e-01 6.62721893491124314e-01 5.50295857988165715e-01 6.80473372781065122e-01 5.50295857988165715e-01 6.98224852071005930e-01 5.50295857988165715e-01 7.15976331360946738e-01 5.50295857988165715e-01 7.21893491124260378e-01 5.50295857988165715e-01 7.27810650887574018e-01 5.50295857988165715e-01 7.27810650887574018e-01 5.56213017751479244e-01 7.27810650887574018e-01 5.62130177514792884e-01 7.27810650887574018e-01 5.68047337278106523e-01 7.27810650887574018e-01 5.73964497041420163e-01 7.27810650887574018e-01 5.79881656804733692e-01 7.27810650887574018e-01 5.85798816568047331e-01 7.27810650887574018e-01 6.03550295857988139e-01 7.15976331360946738e-01 6.27218934911242587e-01 7.15976331360946738e-01 6.39053254437869866e-01 7.15976331360946738e-01 6.50887573964497035e-01 7.15976331360946738e-01 6.56804733727810675e-01 7.15976331360946738e-01 6.74556213017751483e-01 7.15976331360946738e-01 6.92307692307692291e-01 7.15976331360946738e-01 6.98224852071005930e-01 7.15976331360946738e-01 7.10059171597633099e-01 7.15976331360946738e-01 7.15976331360946738e-01 7.15976331360946738e-01 7.21893491124260378e-01 7.15976331360946738e-01 7.33727810650887546e-01 7.15976331360946738e-01 7.57396449704141994e-01 7.15976331360946738e-01 7.81065088757396442e-01 7.15976331360946738e-01 7.86982248520710082e-01 7.15976331360946738e-01 7.92899408284023721e-01 7.15976331360946738e-01 7.98816568047337250e-01 7.15976331360946738e-01 8.04733727810650890e-01 7.15976331360946738e-01 8.10650887573964529e-01 7.15976331360946738e-01 8.16568047337278058e-01 7.15976331360946738e-01 8.22485207100591698e-01 7.15976331360946738e-01 8.28402366863905337e-01 7.15976331360946738e-01 8.46153846153846145e-01 7.15976331360946738e-01 8.63905325443786953e-01 7.15976331360946738e-01 8.69822485207100593e-01 7.15976331360946738e-01 8.75739644970414233e-01 7.15976331360946738e-01 8.81656804733727761e-01 7.15976331360946738e-01 8.93491124260355041e-01 7.33727810650887546e-01 8.93491124260355041e-01 7.33727810650887546e-01 8.99408284023668680e-01 7.33727810650887546e-01 9.05325443786982209e-01 7.33727810650887546e-01 9.11242603550295849e-01 7.33727810650887546e-01 9.23076923076923128e-01 7.33727810650887546e-01 9.28994082840236657e-01 7.33727810650887546e-01 9.34911242603550297e-01 7.33727810650887546e-01 9.52662721893491105e-01 7.33727810650887546e-01 9.58579881656804744e-01 7.33727810650887546e-01 9.82248520710059192e-01 7.51479289940828354e-01 1.00000000000000000e+00 11 38 4 39 9 Save File 20 39 198 3.37563078732056043e-02 3.62059673705354967e-02 3.37563078732056043e-02 3.62059673705354967e-02 3.67938856498946643e-02 4.22811229239135611e-02 3.67938856498946643e-02 5.44804272206163565e-02 3.67938856498946643e-02 7.61844103669589989e-02 3.67938856498946643e-02 9.28420949488021341e-02 3.67938856498946643e-02 1.14546078095144777e-01 3.67938856498946643e-02 1.42031257655185927e-01 3.67938856498946643e-02 1.58639949047082451e-01 3.67938856498946643e-02 1.75297633628925587e-01 3.67938856498946643e-02 1.91906325020822111e-01 3.54220763313899401e-02 1.97491548674734230e-01 3.40012738229386047e-02 2.03076772328646293e-01 3.19435598451815461e-02 2.15472049385135478e-01 3.19435598451815461e-02 2.27720346871784829e-01 3.46871784821909945e-02 2.49571309587967249e-01 3.84106609181323888e-02 2.90823575523002309e-01 4.97770809857429608e-02 3.73769046102591762e-01 5.41864680809367449e-02 4.31287051099897123e-01 5.79099505168781392e-02 4.72539317034932127e-01 5.79099505168781392e-02 5.06442604477977509e-01 5.79099505168781392e-02 5.40394885110969558e-01 5.79099505168781392e-02 5.57003576502866138e-01 5.79099505168781392e-02 5.69251873989515489e-01 5.79099505168781392e-02 5.77727695850276834e-01 5.79099505168781392e-02 5.83116946744402509e-01 5.96737053549556418e-02 5.91788741364950299e-01 6.13884670030865887e-02 6.00460535985497978e-01 6.31032286512174800e-02 6.09083337416099102e-01 6.52099358189211498e-02 6.21478614472588342e-01 6.31032286512174800e-02 6.33873891529077471e-01 6.07025623438341988e-02 6.50678555680760384e-01 5.76649845671451944e-02 6.78310714810641313e-01 5.49213659301356905e-02 7.00210670716770345e-01 5.49213659301356905e-02 7.34113958159815727e-01 5.49213659301356905e-02 7.68066238792807776e-01 5.49213659301356905e-02 8.23036597912890189e-01 5.18347949635000749e-02 8.50717750232717673e-01 4.63475576894811780e-02 8.94468668855029181e-01 4.33099799127921181e-02 9.22100827984910110e-01 4.33099799127921181e-02 9.34349125471559461e-01 4.33099799127921181e-02 9.55024251629023491e-01 4.33099799127921181e-02 9.60462495713095832e-01 4.33099799127921181e-02 9.65900739797168173e-01 4.33099799127921181e-02 9.71338983881240514e-01 4.33099799127921181e-02 9.76777227965312855e-01 4.33099799127921181e-02 9.82166478859438530e-01 4.33099799127921181e-02 9.87604722943510871e-01 4.33099799127921181e-02 9.93042967027583101e-01 4.33099799127921181e-02 9.98481211111655442e-01 4.40448777619910636e-02 1.00000000000000000e+00 4.43878300916172863e-02 1.00000000000000000e+00 4.51717211307628430e-02 9.98481211111655442e-01 4.65435304492675672e-02 9.92895987457743434e-01 4.54656802704423990e-02 9.89662436921267874e-01 4.43878300916172863e-02 9.86428886384792536e-01 4.33099799127921181e-02 9.83195335848317087e-01 4.19381705942873939e-02 9.77610112194404968e-01 4.19381705942873939e-02 9.69134290333643622e-01 4.05173680858360585e-02 9.63549066679731503e-01 3.88026064377051672e-02 9.54877272059183824e-01 3.74307971192004429e-02 9.49292048405271593e-01 3.37563078732056043e-02 9.40179315075204469e-01 3.16985938954485458e-02 9.27784038018715451e-01 2.99348390573710432e-02 9.19112243398167661e-01 2.82200774092400963e-02 9.10489441967566426e-01 2.82200774092400963e-02 9.05051197883494196e-01 2.82200774092400963e-02 8.92802900396844845e-01 2.61623634314830378e-02 8.80456616530302272e-01 2.41046494537259237e-02 8.68061339473813143e-01 2.20469354759688652e-02 8.55666062417324014e-01 2.02831806378913071e-02 8.47043260986722890e-01 2.02831806378913071e-02 8.34794963500073539e-01 2.02831806378913071e-02 8.22595659203370744e-01 2.02831806378913071e-02 8.10396354906667948e-01 2.02831806378913071e-02 8.01871539855960047e-01 2.02831806378913071e-02 7.96482288961834373e-01 2.02831806378913071e-02 7.91044044877762031e-01 2.02831806378913071e-02 7.82568223017000575e-01 2.02831806378913071e-02 7.70319925530351224e-01 2.02831806378913071e-02 7.58120621233648539e-01 2.26838469452745883e-02 7.41315957081965626e-01 2.47905541129783136e-02 7.28920680025476497e-01 2.65053157611092050e-02 7.20297878594875263e-01 2.89059820684924862e-02 7.03493214443192461e-01 2.89059820684924862e-02 6.91293910146489665e-01 2.89059820684924862e-02 6.63808730586448514e-01 2.58194111018568151e-02 6.36127578266620919e-01 2.34187447944735894e-02 6.19322914114938006e-01 1.82744598500808597e-02 5.79295477928567948e-01 1.82744598500808597e-02 5.67096173631865152e-01 1.82744598500808597e-02 5.50438489050022017e-01 1.82744598500808597e-02 5.41962667189260672e-01 1.82744598500808597e-02 5.25353975797364203e-01 1.82744598500808597e-02 5.03600999461074950e-01 1.82744598500808597e-02 4.76115819901033743e-01 1.82744598500808597e-02 4.35059526725785128e-01 1.82744598500808597e-02 3.73671059722698540e-01 1.55308412130713558e-02 3.51771103816569508e-01 9.74964479937290296e-03 3.02238988780559492e-01 7.00602616236345455e-03 2.80339032874430460e-01 7.00602616236345455e-03 2.58635049728087818e-01 7.00602616236345455e-03 2.46435745431385023e-01 7.00602616236345455e-03 2.37910930380677066e-01 7.00602616236345455e-03 2.25711626083974326e-01 7.00602616236345455e-03 2.13512321787271586e-01 7.00602616236345455e-03 2.01264024300622235e-01 9.06374014012051310e-03 1.88917740434079662e-01 1.08274949781980157e-02 1.80245945813531927e-01 1.25422566263289625e-02 1.71574151192984192e-01 1.61677526823771345e-02 1.62461417862917068e-01 1.82744598500808597e-02 1.50066140806427883e-01 2.03321738278379183e-02 1.37670863749938754e-01 2.27328401352211995e-02 1.20915192788202452e-01 2.27328401352211995e-02 1.04257508206359317e-01 2.27328401352211995e-02 8.25535250600166748e-02 2.27328401352211995e-02 6.58958404781735396e-02 2.27328401352211995e-02 4.14972318847680044e-02 2.44476017833520909e-02 3.28254372642202696e-02 2.61623634314830378e-02 2.42026358336191461e-02 2.79261182695605403e-02 1.55308412130713558e-02 2.96408799176914317e-02 6.85904659252362103e-03 3.07187300965165999e-02 3.67448924599478310e-03 3.26294645044338805e-02 1.76375483807750255e-03 3.82146881583459996e-02 3.42952329626167174e-04 4.66905100191073452e-02 3.42952329626167174e-04 5.89388075057566962e-02 3.42952329626167174e-04 7.11381118024594361e-02 3.42952329626167174e-04 8.77468031943559601e-02 3.42952329626167174e-04 1.04404487776199095e-01 3.42952329626167174e-04 1.16603792072901891e-01 3.42952329626167174e-04 1.33261476654744970e-01 3.42952329626167174e-04 1.41737298515506371e-01 3.42952329626167174e-04 1.54083582382048889e-01 2.40066630738328124e-03 1.66478859438538018e-01 4.45838028514033979e-03 2.05673411395815964e-01 1.24442702464357402e-02 2.27573367301945051e-01 1.51878888834451886e-02 2.49424330018127471e-01 1.79315075204546370e-02 2.74067904561265951e-01 1.99892214982117511e-02 2.86414188427808525e-01 2.20469354759688652e-02 3.08314144333937556e-01 2.47905541129783136e-02 3.58875116358826129e-01 3.66469060800548863e-02 3.75630787320562431e-01 3.90475723874381675e-02 3.84302581941110166e-01 4.08113272255156700e-02 3.89740826025182507e-01 4.08113272255156700e-02 3.98216647885943853e-01 4.08113272255156700e-02 4.06692469746705199e-01 4.08113272255156700e-02 4.15168291607466544e-01 4.08113272255156700e-02 4.27416589094115951e-01 4.08113272255156700e-02 4.35892410954877296e-01 4.08113272255156700e-02 4.44368232815638642e-01 4.08113272255156700e-02 4.52844054676399987e-01 4.08113272255156700e-02 4.58282298760472273e-01 4.08113272255156700e-02 4.72196364705305960e-01 4.08113272255156700e-02 4.80672186566067305e-01 4.08113272255156700e-02 4.89148008426828651e-01 4.08113272255156700e-02 5.01396305913478058e-01 4.08113272255156700e-02 5.09872127774239403e-01 4.08113272255156700e-02 5.18347949635000749e-01 4.08113272255156700e-02 5.35299593356523440e-01 4.08113272255156700e-02 5.43775415217284785e-01 4.08113272255156700e-02 5.52300230267992798e-01 4.08113272255156700e-02 5.66165303022879818e-01 4.08113272255156700e-02 5.74690118073587719e-01 4.08113272255156700e-02 5.83165939934349176e-01 4.08113272255156700e-02 6.07564548527754655e-01 4.08113272255156700e-02 6.16089363578462557e-01 4.08113272255156700e-02 6.24565185439223902e-01 4.08113272255156700e-02 6.41859781490372816e-01 3.73328107393072206e-02 6.58664445642055729e-01 3.49321444319239394e-02 6.75469109793738642e-01 3.25314781245407136e-02 7.19220028416050150e-01 2.70442408505217613e-02 7.31615305472539279e-01 2.49865268727647027e-02 7.54005193278134422e-01 1.94013032188525836e-02 8.04027240213610384e-01 1.03865562686786261e-02 8.32198324432903869e-01 4.16442114546078379e-03 8.49002988584586671e-01 1.76375483807750255e-03 8.57625790015187905e-01 0.00000000000000000e+00 8.63064034099260136e-01 0.00000000000000000e+00 8.71539855960021592e-01 0.00000000000000000e+00 8.88540492871490839e-01 0.00000000000000000e+00 8.97016314732252296e-01 0.00000000000000000e+00 9.02405565626377859e-01 0.00000000000000000e+00 9.07843809710450200e-01 0.00000000000000000e+00 9.13282053794522541e-01 0.00000000000000000e+00 9.18720297878594883e-01 0.00000000000000000e+00 9.27196119739356228e-01 0.00000000000000000e+00 9.35671941600117574e-01 0.00000000000000000e+00 9.44343736220665253e-01 1.76375483807750255e-03 9.52966537651266488e-01 3.47851648620839393e-03 9.58600754495125162e-01 4.89931899465972931e-03 9.64185978149037282e-01 6.27112831316445352e-03 9.67419528685512731e-01 7.34897849198962172e-03 9.73004752339424739e-01 8.76978100044095710e-03 9.78589975993336969e-01 1.01415903189456813e-02 9.84175199647248977e-01 1.15623928273970167e-02 9.89809416491107763e-01 1.29342021459017409e-02 9.92993973837636545e-01 1.40120523247268536e-02 13 6 Plugin 1 0 40 11 grid/slot_r 26 Place view in grid (right) 21 41 254 9.91764705882352882e-01 4.02846299810246666e-02 9.91764705882352882e-01 4.02846299810246666e-02 9.94269449715370013e-01 4.10815939278937337e-02 9.96774193548387144e-01 4.19165085388994441e-02 9.98671726755218203e-01 4.47628083491460882e-02 1.00000000000000000e+00 5.14800759013282483e-02 1.00000000000000000e+00 5.80455407969639459e-02 9.96166982922201072e-01 7.14421252371916782e-02 9.89449715370019023e-01 8.93927893738140633e-02 9.82694497153700164e-01 1.07381404174573036e-01 9.75218216318785580e-01 1.29810246679316899e-01 9.67741935483870996e-01 1.52277039848197349e-01 9.63415559772295982e-01 1.69582542694497129e-01 9.59582542694497165e-01 1.82979127134724862e-01 9.56242884250474434e-01 1.92960151802656521e-01 9.54914611005692526e-01 1.99677419354838737e-01 9.53814041745730590e-01 2.04003795066413640e-01 9.52485768500948793e-01 2.10721062618595800e-01 9.51385199240986745e-01 2.15047438330170759e-01 9.50322580645161286e-01 2.19373814041745718e-01 9.49222011385199238e-01 2.23700189753320677e-01 9.48159392789373889e-01 2.28064516129032280e-01 9.48159392789373889e-01 2.34629981024667922e-01 9.48159392789373889e-01 2.41195445920303619e-01 9.46299810246679307e-01 2.54212523719165084e-01 9.44705882352941173e-01 2.63776091081593944e-01 9.43074003795066451e-01 2.73377609108159392e-01 9.38747628083491437e-01 2.90721062618595816e-01 9.37153700189753303e-01 3.00322580645161263e-01 9.35559772296015169e-01 3.09924098671726767e-01 9.33700189753320697e-01 3.22941176470588232e-01 9.31574952561669889e-01 3.39867172675521800e-01 9.29184060721062632e-01 3.61309297912713490e-01 9.21973434535104386e-01 4.04535104364326381e-01 9.19848197343453577e-01 4.21499051233396593e-01 9.17722960151802658e-01 4.38425047438330162e-01 9.15597722960151739e-01 4.68254269449715377e-01 9.15597722960151739e-01 4.77741935483870950e-01 9.15597722960151739e-01 4.87191650853889935e-01 9.15597722960151739e-01 5.06129032258064493e-01 9.15597722960151739e-01 5.18994307400379551e-01 9.15597722960151739e-01 5.28444022770398480e-01 9.15597722960151739e-01 5.37931688804554109e-01 9.15597722960151739e-01 5.50796963946869056e-01 9.15597722960151739e-01 5.57362428842504753e-01 9.15597722960151739e-01 5.70531309297912737e-01 9.15597722960151739e-01 5.83396584440227683e-01 9.15597722960151739e-01 5.96261859582542741e-01 9.15597722960151739e-01 6.09165085388994276e-01 9.15597722960151739e-01 6.35426944971537067e-01 9.15597722960151739e-01 6.52277039848197404e-01 9.15597722960151739e-01 6.69089184060721043e-01 9.15597722960151739e-01 6.98804554079696327e-01 9.15597722960151739e-01 7.15616698292220077e-01 9.15597722960151739e-01 7.36907020872865304e-01 9.15597722960151739e-01 7.95009487666034076e-01 9.15597722960151739e-01 8.11821631878557826e-01 9.15597722960151739e-01 8.28633776091081575e-01 9.11385199240986710e-01 8.62561669829222000e-01 9.09259962049335790e-01 8.79487666034155624e-01 9.06603415559772308e-01 9.22751423149905103e-01 9.06603415559772308e-01 9.39563567362428853e-01 9.06603415559772308e-01 9.60853889943074080e-01 9.06603415559772308e-01 9.70303605313092898e-01 9.06603415559772308e-01 9.86356736242884224e-01 9.06603415559772308e-01 9.90569259962049253e-01 9.06603415559772308e-01 9.94743833017077805e-01 9.06603415559772308e-01 9.95806451612903265e-01 9.06603415559772308e-01 9.93453510436432596e-01 9.06603415559772308e-01 9.83965844402276968e-01 9.06603415559772308e-01 9.74516129032258038e-01 9.06603415559772308e-01 9.65066413662239109e-01 9.06603415559772308e-01 9.55578747628083480e-01 9.04174573055028463e-01 9.44573055028463004e-01 9.02846299810246666e-01 9.37855787476280844e-01 9.01518026565464869e-01 9.31138519924098684e-01 9.01518026565464869e-01 9.14326375711574935e-01 9.01518026565464869e-01 9.01461100569259988e-01 9.01518026565464869e-01 8.80170777988614761e-01 9.01518026565464869e-01 8.46508538899430674e-01 9.01518026565464869e-01 8.25218216318785558e-01 9.01518026565464869e-01 8.08406072106261808e-01 9.01518026565464869e-01 7.60815939278937314e-01 9.01518026565464869e-01 7.44003795066413676e-01 9.01518026565464869e-01 7.22713472485768449e-01 8.99924098671726735e-01 7.13111954459203057e-01 8.98557874762808351e-01 7.06432637571157485e-01 8.97229601518026554e-01 6.99715370018975324e-01 8.96166982922201205e-01 6.95388994307400421e-01 8.96166982922201205e-01 6.88823529411764723e-01 8.96166982922201205e-01 6.75920303605313078e-01 8.96166982922201205e-01 6.59108159392789439e-01 8.96166982922201205e-01 6.46242884250474381e-01 8.96166982922201205e-01 6.33339658444022735e-01 8.96166982922201205e-01 6.20474383301707788e-01 8.96166982922201205e-01 6.11024667931688859e-01 8.96166982922201205e-01 5.98121442125237213e-01 8.96166982922201205e-01 5.85256166982922155e-01 8.97229601518026554e-01 5.80929791271347251e-01 8.98330170777988601e-01 5.76565464895635649e-01 8.99392789373814061e-01 5.72239089184060745e-01 9.00759013282732446e-01 5.65559772296015173e-01 9.00759013282732446e-01 5.56072106261859544e-01 9.00759013282732446e-01 5.29810246679316865e-01 9.00759013282732446e-01 5.12960151802656528e-01 9.00759013282732446e-01 4.91669829222011412e-01 9.03111954459203004e-01 4.48975332068311195e-01 9.05237191650853923e-01 4.32011385199240983e-01 9.07362428842504731e-01 4.15047438330170770e-01 9.09487666034155540e-01 3.98121442125237202e-01 9.09487666034155540e-01 3.91518026565464916e-01 9.09487666034155540e-01 3.84952561669829219e-01 9.09487666034155540e-01 3.80740037950664134e-01 9.09487666034155540e-01 3.74174573055028437e-01 9.09487666034155540e-01 3.64724857685009507e-01 9.09487666034155540e-01 3.55275142314990522e-01 9.09487666034155540e-01 3.42371916508538932e-01 9.13472485768500930e-01 3.12428842504743842e-01 9.15332068311195401e-01 2.99411764705882377e-01 9.16925996204933647e-01 2.89810246679316874e-01 9.16925996204933647e-01 2.80360531309297945e-01 9.16925996204933647e-01 2.70872865275142316e-01 9.16925996204933647e-01 2.58007590132827314e-01 9.16925996204933647e-01 2.45142314990512311e-01 9.16925996204933647e-01 2.35654648956356738e-01 9.16925996204933647e-01 2.26204933586337753e-01 9.16925996204933647e-01 2.16755218216318768e-01 9.16925996204933647e-01 2.00702087286527497e-01 9.16925996204933647e-01 1.94136622390891855e-01 9.16925996204933647e-01 1.89924098671726771e-01 9.16925996204933647e-01 1.76793168880455431e-01 9.16925996204933647e-01 1.70227703984819734e-01 9.16925996204933647e-01 1.63662239089184036e-01 9.16925996204933647e-01 1.57058823529411751e-01 9.16925996204933647e-01 1.50493358633776109e-01 9.16925996204933647e-01 1.37628083491461106e-01 9.16925996204933647e-01 1.24762808349146104e-01 9.16925996204933647e-01 1.15275142314990531e-01 9.16925996204933647e-01 1.05825426944971546e-01 9.16925996204933647e-01 1.01612903225806461e-01 9.16925996204933647e-01 9.21631878557874762e-02 9.16925996204933647e-01 8.26755218216319032e-02 9.16925996204933647e-01 6.58633776091081535e-02 9.16925996204933647e-01 4.90512333965844594e-02 9.16925996204933647e-01 3.61480075901328135e-02 9.16925996204933647e-01 2.95825426944971714e-02 9.16925996204933647e-01 2.30170777988614739e-02 9.16925996204933647e-01 2.06641366223909162e-02 9.16925996204933647e-01 1.40986717267552186e-02 9.16925996204933647e-01 7.49525616698293318e-03 9.16091081593927825e-01 5.02846299810244535e-03 9.13586337760910805e-01 4.19354838709679045e-03 9.07020872865275107e-01 4.19354838709679045e-03 8.97419354838709715e-01 5.78747628083492449e-03 8.84402277039848195e-01 7.64705882352939570e-03 8.74952561669829265e-01 7.64705882352939570e-03 8.65502846299810225e-01 7.64705882352939570e-03 8.56015180265654596e-01 7.64705882352939570e-03 8.37115749525616737e-01 7.64705882352939570e-03 8.30512333965844451e-01 7.64705882352939570e-03 8.20948766603415536e-01 9.24098671726752974e-03 8.14231499051233376e-01 1.05692599620493266e-02 8.04629981024667984e-01 1.22011385199241040e-02 7.94648956356736269e-01 1.55028462998102468e-02 7.87969639468690697e-01 1.68311195445920436e-02 7.78368121442125194e-01 1.84629981024667655e-02 7.68766603415559802e-01 2.00569259962049551e-02 7.51954459203036052e-01 2.00569259962049551e-02 7.35142314990512302e-01 2.00569259962049551e-02 7.22239089184060656e-01 2.00569259962049551e-02 7.12789373814041727e-01 2.00569259962049551e-02 7.06223908918406140e-01 2.00569259962049551e-02 6.93092979127134745e-01 2.00569259962049551e-02 6.86489563567362460e-01 2.00569259962049551e-02 6.82314990512334019e-01 2.00569259962049551e-02 6.77950664136622416e-01 2.11195445920303593e-02 6.73624288425047402e-01 2.22201138519924069e-02 6.69297912713472498e-01 2.32827324478178110e-02 6.55483870967741966e-01 2.43833017077798586e-02 6.46034155597722926e-01 2.43833017077798586e-02 6.36584440227703996e-01 2.43833017077798586e-02 6.22922201138519926e-01 2.43833017077798586e-02 6.18709677419354787e-01 2.43833017077798586e-02 6.14497153700189758e-01 2.43833017077798586e-02 6.05047438330170717e-01 2.43833017077798586e-02 5.95559772296015200e-01 2.43833017077798586e-02 5.86110056925996159e-01 2.43833017077798586e-02 5.72447817836812201e-01 2.43833017077798586e-02 5.68235294117647061e-01 2.43833017077798586e-02 5.64022770398481921e-01 2.43833017077798586e-02 5.55597722960151752e-01 2.43833017077798586e-02 5.49032258064516165e-01 2.43833017077798586e-02 5.42352941176470593e-01 2.57115749525616555e-02 5.28956356736242861e-01 2.84060721062618371e-02 5.22239089184060701e-01 2.97343453510436340e-02 5.15673624288425003e-01 2.97343453510436340e-02 5.02542694497153719e-01 2.97343453510436340e-02 4.95939278937381378e-01 2.97343453510436340e-02 4.89373814041745736e-01 2.97343453510436340e-02 4.76242884250474396e-01 2.97343453510436340e-02 4.69677419354838699e-01 2.97343453510436340e-02 4.63111954459203057e-01 2.97343453510436340e-02 4.49829222011385199e-01 2.84060721062618371e-02 4.43111954459203039e-01 2.70777988614800957e-02 4.29715370018975307e-01 2.43833017077798586e-02 4.12903225806451613e-01 2.43833017077798586e-02 4.00000000000000022e-01 2.43833017077798586e-02 3.83187855787476273e-01 2.43833017077798586e-02 3.66375711574952523e-01 2.43833017077798586e-02 3.59810246679316881e-01 2.43833017077798586e-02 3.53244781783681239e-01 2.43833017077798586e-02 3.44819734345351070e-01 2.43833017077798586e-02 3.40607210626185930e-01 2.43833017077798586e-02 3.34041745730550288e-01 2.43833017077798586e-02 3.14573055028463000e-01 2.43833017077798586e-02 2.97760910815939250e-01 2.43833017077798586e-02 2.71992409867172658e-01 2.43833017077798586e-02 2.65426944971537015e-01 2.43833017077798586e-02 2.61214421252371931e-01 2.43833017077798586e-02 2.57039848197343490e-01 2.43833017077798586e-02 2.52827324478178350e-01 2.43833017077798586e-02 2.48614800759013266e-01 2.43833017077798586e-02 2.44402277039848181e-01 2.43833017077798586e-02 2.37722960151802665e-01 2.57115749525616555e-02 2.31005692599620505e-01 2.70777988614800957e-02 2.24288425047438345e-01 2.84060721062618371e-02 2.14724857685009485e-01 3.00000000000000266e-02 2.08159392789373787e-01 3.00000000000000266e-02 2.01555977229601502e-01 3.00000000000000266e-02 1.94990512333965860e-01 3.00000000000000266e-02 1.88425047438330162e-01 3.00000000000000266e-02 1.81859582542694520e-01 3.00000000000000266e-02 1.68728652751423125e-01 3.00000000000000266e-02 1.64516129032258041e-01 3.00000000000000266e-02 1.60303605313092956e-01 3.00000000000000266e-02 1.56091081593927872e-01 3.00000000000000266e-02 1.46641366223908942e-01 3.00000000000000266e-02 1.33738140417457296e-01 3.00000000000000266e-02 1.20872865275142294e-01 3.00000000000000266e-02 1.08007590132827347e-01 3.00000000000000266e-02 1.01442125237191649e-01 3.00000000000000266e-02 9.72296015180265649e-02 3.00000000000000266e-02 9.30170777988614805e-02 3.00000000000000266e-02 8.64516129032257830e-02 3.00000000000000266e-02 7.68500948766603353e-02 2.84060721062618371e-02 6.38330170777988704e-02 2.65464895635673659e-02 4.64895635673624463e-02 2.22201138519924069e-02 3.30929791271347140e-02 1.83870967741935343e-02 2.34914611005692664e-02 1.67931688804554002e-02 1.68121442125236942e-02 1.54269449715370155e-02 1.25996204933586098e-02 1.54269449715370155e-02 8.38709677419352539e-03 1.54269449715370155e-02 4.17457305502844100e-03 1.54269449715370155e-02 0.00000000000000000e+00 1.54269449715370155e-02 13 42 11 grid/slot_l 25 Place view in grid (left) 7 Default 0 0 1 1 43 0 0 2 0 14 44 0 11 45 12 117 0 15 46 0 11 47 12 112 0 5 gedit 0 1 1 1 0 0 -1 0 0 0 5 0 21 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 0 0 21 29 0 0 0 21 0 0 21 1 0 20 20 1 0 19 19 1 0 18 18 1 0 17 17 1 0 16 16 1 0 15 15 1 0 14 14 1 0 1 1 1 0 2 2 1 0 3 3 1 0 4 4 1 0 5 5 1 0 6 6 1 0 7 7 1 0 8 8 1 0 9 9 1 0 10 10 1 0 11 11 1 0 12 12 1 0 13 13 1 0wstroke-2.2.1/example/meson.build000066400000000000000000000001121472137506600170140ustar00rootroot00000000000000install_data('actions-wstroke-2', install_dir: conf_data.get('DATA_DIR')) wstroke-2.2.1/icons/000077500000000000000000000000001472137506600143405ustar00rootroot00000000000000wstroke-2.2.1/icons/128x128/000077500000000000000000000000001472137506600152755ustar00rootroot00000000000000wstroke-2.2.1/icons/128x128/wstroke.png000066400000000000000000000164501472137506600175070ustar00rootroot00000000000000PNG  IHDR>agAMA a cHRMz&u0`:pQ<bKGDC pHYsppw"LIDATxwǿ{PTD ( (5A%6bbT4јyFŒؕbFc(56+ (UJrg1-ݽwg޽sfҸXLP}_?FLt[V`N#h+lh{&]bk ZLׁ#P{^K?r7zz)7_Q֒rS?eeu`PZK'u1q (%seKM ̊@ Njr'!ŋI1/R f+Qn-qf PZfh=@E~J*p}BI$RuxlL9"'Ҫ{:g_~Y֓{ dc)J:nXy`l q叱+@fWpq&iU9m2N*ə <.tڈc &(C^as'In<:јo- 5n,F`'k P SMxqXqtGU+ r/:A*$:iPDKO| +(`Q#qmefEC;$t`W!R̳"X zTmWF_}c(CmnԀƷ;LJkbmUH|C kw/QMX^C21*cg֢šao.!2HW4e@@mgjCEPNoC?`M'X?[_-1<+bP],tꞀ5^0y?0g6LyYΫ֩0vF{U]`^-j *Ժ}Q= R6)m<<66 j,t[ٞUN&A+Q0 /Fe4YM4z[ -4Gy|s!;H>Y_ EԼΘn=>=>N9Z vYzk2J`JhsnvK~!`&~NMKaE)Ԟgj^aB G:C=KiE-Q ܇gobev\;q&=c)4$lb ); QY** `ʱlN eW Ԙ 噓ZWDe`1('sm=9(ο^7 ,3/"qbJW yX+IЅD޸kڠ/*=#^K|ȎU^<@{cTx~8t5%by9-ukW=!.kv]@O40^I͠Z\\֐ w֍R 2/9EyĨ'vEeu& Bep+vq _#b@ &@ÓN}zK9=#\:\k`I/f.9Qs J#*~ߗ-?dMv+n G_O^GSDqlJ~ W Vf{YR%A&^ᔬ}z(>9 1_ύ;RϺ`X}E?:M(%k#ė3&v_r;Et$ǀRkow0o(ʸ/wkn󝡼V܄i@XM!sK` oRm\AYk<0^[L8{7J>㆗O5N'8q"U5uv-ay@͍V*&.^? h@Q/f=*X."/ŸM#ټﱍa; ۓZ58Aֽ|825[B1M ru>|}^Biu+Vre 3Aw~&>d*uhUR)3}tralqPgɫ/jdI)R?+@qE_`:/ `/[\;|Nl:Z6άtqlP̌x4,*qgT͆SCs,3xsQo8()`pb2 %8CfO_H1F  ?D0hbc/"dO9*tfYܼuv vI&Ω^XG? +w^f:V, F$RQs+n~ nYJXP5E~qO;)gvţb$8AN'pEtHVzFJ\ܢDu T{M#V8(4֤jw(F`E`~HfH;\DM*n@Ye{rnԅG}Qplcp|AIxAM#hA=p(li,0" rV.SJ2ee1O׆@[@!Hj [x| hIJ AET?ޤMm)& ϛ : m$@rTd̈́u/^:m͡-ot Tgp`^nȧuT{S:`|:TK:c`):*ݤp/)bU{^{[T (G@eH5z4*Nsz;^77I'7o|egkmvM5Y߈0*%#$*f1y\~3q}Š5}ЀYRJ݄$5*dYc<)M߷ebh4jɶ3E.W@7A!W~$oÏ: iL3 \(˘{R6Z *qBPE?NWhjb ƻO?I;dQV=":>V4d)>5^$W3 KC/aG}jL=#HufR[nzMUԹliu?I/au͇q S'!jYE!7{3󛊟'V!%95tA:DR- ó߾09|3] ۭ)YSpx,k1-,I!5 4]%m7!esH\̍0'3NכVd*EܻS8zO7NHU\|HЉq7?;$d5[k3s'$ c}kGդu=(<~JόtC?8ݗQ@<:ӘsX_~x{=A Sdp+CHlg?4kǡ4\G;^>yh EO[L挡,ㅃ[ز\N &O9?[k3[خ*=< #XA dE}?N56U 8&(0`ern/`lŴE=l3HHb9{63նk#ZJ>^kzy qt -o69+U1i3"J*e;Dx'=.B%&![X3aZ"յ~6Jl`8@ϊUg1MD_ &8k%u`EDq32>4uw['= ]?Ɯ(cS1A&*YheW6t\/F܈?GM'ke / ]Cx pP1[.|C^[i:B*].9 rR?f1E߄mLJG̿ק!յj*N9$z2bhy{8n) ka- .ً^톶'R٦ Lx9G՟q 4T.@fOۏAd | 8ŕ8iQGw.0O'Xᢉ>ڡhuf)l=PX!Z:6 c@u7ca[7>_b▍C@T9 Dg$Q6oAeA " z/.7ϒx|epL?1 : 2V=/\B>:_Tlv+_ ݖTbwA 6~ݢQr'} *`M~\gMitůHV}̛XX2bޛQOVk[cRoQ! N4WJT-\jQ ^Z7QsKļH>jK&N99T݌R4jxle au) >kfB@Gmt4MmL<\>ɗq|[C8^y%^gPyb UE4wr_>c3w G~S 5}pT":n@Q.Gx ᨹ 븺lՇ#(Ne kJ*bXp?p@ /7S?:/# 1^1HOTi+Ǩ5+_26aRˊqL"*;-m[:3I+g-[@ӧ. Oξ !>wX0p]aDD]1c}H۔X(+QW>B뻇^\񈜌r(иQ…d}lSJ=6# q+ a`#Ex Kq7o5y%{΋[`5m񜝁t%%@*I6b]Vl6l6l6l6l6lV?fJ%tEXtdate:create2020-11-28T12:50:45+08:00#%tEXtdate:modify2020-11-28T12:50:45+08:00tEXtSoftwarewww.inkscape.org<IENDB`wstroke-2.2.1/icons/160x160/000077500000000000000000000000001472137506600152655ustar00rootroot00000000000000wstroke-2.2.1/icons/160x160/wstroke.png000066400000000000000000000223301472137506600174710ustar00rootroot00000000000000PNG  IHDRg-gAMA a cHRMz&u0`:pQ<bKGDC pHYs#IDATxy?V̀( ( ŠKpKF5`7%Kb4&&.1jK4"DU\FQ]@MEaa^nV=8Uunos:蠃:蠃:蠃.HTgLk`l\+Х֦6!ӯ {#쉲m3| oy3QW׎Q`|Sp?GMD]J%ܟb3Hg&.I+~%yWr-T QZ<6tiԷؼxﻓ] xNǤbr+߫'YsOF}{*W\q7p k /]*H_mޤX[[ź-.=0Wg= >QݪJ`YWoG?s& عr'jNbĺݨ!| QsO1LG/\l-Yct߃d e|h* ঑T<e "rw"e +S@no" QrNv}LbO9 h`@u<ר_'ou@4}^+`L.T=i?0W!v4ׂDMfi2oFNmM{G׃D5%Y|~ބ+H0<]ϰ?7w"h7ӣ~/?I\yA&2 +7~#_S(n8}}판9 _8;,zS#8z8k}~GƂgHۿ@(:p5[Omdv%~ߝ0J>\0jx`z8sNz*jKAj !ŷA!>i3&Q_ ڏϾ*uPUY|b!ʚΘvқ`$e{bk6GtddG5P_"lQgEmzF&:3mQ^LG ci`,TӲ% *Z|Nr/q3;Emz1|^pV䏳Bfl'|?aF;eyQ]L*_ N|J*~2H)KC}ܹ%Lcf`mmgOp*+ \{֪YG:CGf`NeUVqyGmr^:i%߃nŠLv.r^zfڤ\(bPo!iwa{\gDmra,x&LՅRĸL- 'k9˩Jޕ)Gn~6hzr2_wQ*`}NeT>% v'ɅP4zc0Q[tLqM^խc6*S!NTv󋧽@$ ᥏'9q3KKZ2*OXި-͎%{ !'@kqґ'6daQ/'@>eʔ7=Ne67_*O8vNԦ+8ܝq %`ojܒ)9aCIP,Ž[.&>q^ؒ3}bNePYLcwݬ7_A׮KQYW[QZ6DXr$O* :ࢨ--|F;EmnX*Ll?6ky?jC=9MȨ KS(ē%[ /kJ(--3/Cr+ RTV (v lRPӰk :EmjX*K1 ^A(KF:qsa *K&;iO,eNTw԰T=?&sXX.NSYT:yB:UpN0 궵p+ >*SYnQS9 ܞ*>ޮvo,5k|/g*^,\ 4VGmhIOEõ^] VBNEx[~ew /= @ YJhg #tEH5{x*_}VޙI# ̉B8U*=r?ف7XvE`*B\f!+֩P[6}&ʦAl̞o GTZxuC-_(OZL׮TN-u ZOc}xzǠ:ѭ 5x p3! ~,~o n<6yO@+Q ؉$㰴|x/|>n vSjcSbl4A`6?@[[0 | z +Y"68G' YՕB>Z%,vv7-"m,K(/&@;MW!kFlݨg{NE&0OCFbIHwmVvޟ ^A9%@-G]|/mުa鈿m !7l6s}}m\jQ,bO}կeѿdEI,K|PRV\xv?q9 #B5[8kHU5àXnɀ>wSYTȜw`;ϼwJָ}{:,͚B>|8Fih*NxW.;)Q-ދ'abmaiax][A:4̻Is]wAiχJ!r7]DYeT ;/hdo ӆcOݺ(/ ]ͻ 1Tx{x"/zD72''@ip?[O$7_]ekPR Q֡5l G70 3H tA3 ⹧"cu,teВ/{+]iUL }]0C%_#w軈.[Ͻ\/'H`:{(t;#8.d⿕s B1^05=^F)`!c DU$me]5]^͎Ka"k-iU5;/! v~,y+c )JiV>]ORW}fҭ%ٔ%'"``h?gy{5a%|G(@\@xH9H93)B=a\E)/H%1&Vw(g%D? -Du?2ߓ'AE9D55U|(Q_]"Q5g=-B&oRU4GbOw M ۖ锜JW>-oJȷS;ֻogxC3iހќ5ץ̌&O&Bϣd<{;u_rḄ>z(? 0r7bLaޡ5@x99kKZ(w9\?ʝU`Ѡ~\ C>Π:IGVtX6j |5AV,j2ټ4r|YDjxm9o)]. -M`ߴ:KlC-s?~[Ao-(mE1o2O8i㒅}42z_#EƉ nƦX-u;zKS @rʿ-|z/$Y.x?s"P !MSAI[?iIvϧ^@oNe/굇=ǰq:6lzSN۩vj.x 5)Tq g-+r\IɏWw˝=WoJ^c>NFؾA_ρ_? M놇9mN>g, #>@'*/o \Ӽ ET|pހ%@C lP1vJφ5%plןGLq'!S~ADSݞ.5WW+i:W r9m!]@ 5gc޼+f:N-;˗41-|f8ϫcYBFDž_떪(琨ѥ64  6쮤`8~~H,'}x$6h-nn q`u Wpm<KH\]0qz-F 702>l%ӫKjC3AeN!5H8?mo @OHZ5ClOn|EQ_*ΪFש|: |9?ue+. GIVS)_r9ϪcP\̊=__p^#Nc2}&ýMHUnׇag8?\M[@w,_fMtQg_ι\Zn7v,x+X<_[#L# BU J~ %c3k,X Zh%wR#?mTj-\rctv""jLFHV狮M5ϙxMvHn>JSJnj|}"j7a7NPl܁Qt XSUK9i!9QiY#Ϡ_y+۸7ih8YbTVƋOϴEֆm6smpl5ۘ1sz 2ڱ6v.ZY[~ϾmXG2>((&B0*4.2:[&Z!U~C+o=Jy H8 xDk" ~b1XTnwCQ8@NwN-r}fC,{ ze*cvۍ!E"y]7?ElvaRs1ɩه Yb KG J7MW"m9;bK2Ӹ݁(=&Db=qÐ ?9w";$- [3{5?4caNR}Fm_zȦIeJ>$fC+aQ7$S^ti7'Ȍ[Du} obҭϢIS9,I=-)>q8舖fHJ^d,~Qۘ2ϱs@Yg@SzͪK'$$ejr_c:6V<`]<:qKKS׬(W:W'#v:ʁ+psNʜ1F%i(#N_>r1B{ ̭ǹ9SAuK+ ]W|{|p&Ϙt}@7%A(Sm!`cfsMvA!(ǴKlܷ~MozUu:Dkn i7^5RwDn;8N CgVa{4NʦlQߺPG@ClV@7S$Xui"I=jЀlfDDy !=mf1SlcmD]7!ĦTq},qHsZ!t5iʖ ~d渰Mnϭ45<܃vŬ)t-j)@_:^è /C4]sS!sRgğ zf76*[K$*A;9ӏ YJsyKx%+ CK\Z'1qzbY8~>|B9ޟ])+;UYid?B9gG*9ksfZ1: L a--EFsG1N)Ts{$S䎟3ΪF^IQs˜ijpzZh2:̄#riv4$De XfIu {xϐb Xշ2 *q48euIYƽGRaTSS|дk7G6ApKqTj( h#t|P;g(Ĝ7z Z@QY-5@L<“a07kږADw>G^q3= E4ާx ijKwyzov\bos(9EF#uʪn_V:^@\?_5I )p}b(?1+ 1-#ve7o !XV0l/rc 6 ,5)Dӹa]^'Ex|h,-25=ސ!%_&Xկ;:e!\ @u}>ҏW:]gGdz$*K˰cҸd5*PEH8j֫9sCOhn}Щlr%Q. F`2h^o#P^ܫi;7 w>WNXpr:͝ ܫ7ߥGX/Y7EoVJ[|Tyv?j^ 2͈!tv=;9T7Rz?r`׬7 LZV,t%8uuNZ!\?Ypr"o:Y(Es5nMvv*o457.Q)f _ć;z}]# Z.6Vb>'@/3gR?~!p,?xe2?L3Ę'i BU\Օ>F=Z6v|?$fDΠ-OIzTYxcyyυy}lD&Sxk[k8w7Xr7:ߔl;?kK}`1w@?BY1_%ǘ.Fʎx}'a\]sC`Tb#Lz‰@3< G4g{wTi>܎< 3KM,հibɸ*L^I½? @-p[A s^[/V]MEe|1+#{g+ѺJߏ!%^ E@zH&rܗU"8;ۉ5٥qׁ!8|'tZ$?̛܀SNy*ۈ5rkpL)H4?TzD-00A13&lyDp XI$7P9+{2[Ŕ7ޔF} Q5@ RW8ڝ ΏrEq4FwD} Q5I hI>v\Z \|61xͥE q616qMPwۢ6o\r,S-loB4ҟv0/H%׸ qy@mm BH.=j yhwZ/6mcAYɨcp8'j Z.g-״e6щ2\q{Q^PvVӰuI F-[jk@uO;!1Q^Lx&{AȈYEk vXFn /HH[(}U/i1Tm+/VL=3<ǫ:rzԪ4c6k^FTx`s.{q[Dz1(O9*ĝ֑q_rdMTG;-jՋA@mr4˂]n瓌B;s{.$%8~M ʔՎcF~GO5rOšgȨU/4g H V;r]-r2@Nv|+cvZBQ>_yQ]<@*oqM-%rEvIUF%^څ c6ՇV?;cew.c"[Sp0jKuKVouPbU gVEpI#Gr!(#^~LٞED$nϳɂoۨ%5qg:uv,:Z_5Xٽ z0 DumJz=F;;or?'g==Gh#?M 5S~_!&уWውZu-\'V =@6tKz1ObDbx&KvQ!tc6 Y5VN,l o~Ubѡvoc@+IÁh L vDQ|Cp_Z&@;+s'6YN%!j,M?w7%}cb&YYߎ\ 6hl` %Yo(1i3nw xȸ^A|.$zp F,k(3ӹpP[ͧU"TN:.d$DP`d.^t<hWs^ ]S77 Jy^r%Z%+^hǀ>PGq'xjg ? BtpL6{( @7Jpmw3w(σ> h|.k"V GܝnX[}}mCbxI{ E %j*2`6_B]+^ b_P?b ;`)l ,μafI^8r*x @n𐅃w^Gȿ]$RFZ@xL/^9 ' #gϨB""(=;=te? 5թtK^Ft~Y>HC݅xv /xsZ(օ:G_ x?6wd-UI,]]1pA -be `SЃop&976? h2zK _xsxT6&5:(;3#P /\|#l(0;,r! nO-tsD8ЭہB{q/4Z"  w76f4G{q 6{. wv1tzC+xqx)A}w-Qsp,`M!-۽qQ#`@Mh5 1~\,[.I595{.Hgje@Ie2D~1R 3$wypSKN,YC, gV~q`>ظ}oZ`y]I! E_NiJ2pms#e͵5E4ARKpڭ+b ر>{s1bV GF 󡮵Xs;_%n牜w1kD^G-1DĜ7ӹ?/Kk]KnwC2<z;I?X˯>AW~DϥS@n/!v0R`gz"b)o|8ӄ;B_k ywC> kDW&1w۫$$V?"Kc(vK) )`Z;y7ݸ-?wݩJV.YxKǩ:Ϯ^{=gC@ൽd]a5yki;v 舘-M"qнCXݯ*;λ &%8P_չh-s0Wu  Ǡ: we4p10 Ɍhڑt`]yATv &^-^wk! ml\e&H:vE[0?7 2ky[s~樓0NG.sRl4TO]R mC5kPF~ V3~[A^ $|@= 1<6vBvQt~`W5I®  Cy@NYX?^/e䡨Ĭv܍hmTcp)=}͹aDdsSHøo&F\ J9 RNsgۿ1ermg̃Ka6ޜY[ }HUmPz~@SgiywL$\8ilU&wH4\r`hy_:U6 2rםC.Ubz5NebMF쇓bCJg_f}QB|.UGBouS h*7pîrv4fn0_7ԯm] 29/xe-琩mW.W^MTnc GAt?zƨQ 0][ `s'g!= o8aTb&{RcU|ZGe+3!GzG SZ0a)?;sx3c u>~|<3~' N߸O:9oz)Rm0)+܈¼8 8rmԪFK:+ѝ;*ZKu.VݐRUG^wZ|Sx#V?0 ^gS>POZKc3_[j4祝e*W*9!jgF;i9!6H5swY":>_V,cU?7)X*c4i8Q |V /=+jխ+ ^^@U1n^ci!("g?3E:Ǫq`"T,*>/.+F7 QP#YUO:5-$p,+պ[x"6%E?Mf2NM;?1?G6"b$ĒĀ,7bV5^ƢlaPvTNky.$[? 軤p6t7lfU ; ^K z74:[h0Q`U p+1Q>8~bYoǴ X)]iIӱؾ۞D$9{ǠmsK6=!Gdžfz7 n:%dr!vuBQvH [T&XȲ Mrksn5܃U[II ytcVWx˞*ٍ@#ANrKmڣx{pO1+ w zu)7Kёr\aV<\s 2~ =>pc_lBdy߳S LF[!ھzL3p 0mchuA7Lzi\Wg%1CfIJu2ai|u䝓o30gfE:wh^t:?R"7ڐB#x @izcP9:w2umjXZ!$d.lluqx+7bƶꌝ$G"|^58΃z|=pBpd<4%(ne%#^g ))rm_փQYJ^Yp;[b# kv낛 8 I-1v!;i_3L.Ѯ8'\S1= XYݞ98wc¸c6u٧]n m orwc@<ݘt+ 1b8YL;New{n;ж-ɛ<3O|)~RV ,:8v̉FcwL#&QLe1'>xwr:ߠe ;q] sImȂMǂd,ᖘ˘v̙m>1+gt(繍@*f0s{%rr\]Y{p3rP_sH ϱoK{'6{M!.\s#+Om;lk)5+Ș 0hceo2$(:'6V(RNc'@4de}nZF8˴N# TC*jVO͘VH&N^E:q MF OaG kZY1g-ef-;YJ5PK0us05ʟSo^AMhл}cg@_Mu3xnwʘc\;Zuaejo_m)Q?s)BA;cn;c%"OZnX*8~+$"xa,jW(6|`O}@FXϯ%ܝx7k}/dt5!2nzϵx|ǁⵛ#ncߧN&F&gnsnf]Wב Pm5[\rD}{cֈqc;(:t~{,#cVr,֧Xt}g4 l.tB ne#8QcNfF2qri )“{\+-UH6!n8 (lf[uяƴJD~ :No{wꇠ?#?Oqe7F^h+lŦ$juG`ƈY1\\bI$K&6o@R ~ }7/^6Z_< 'y.1o{ 奾đ9{n`ᇽ 3%MG7!(éc2,YE=\1")[C6CtgDM%,DGs |;q(J t"KN)@#!l@E\_Vk҄8~q,"&;%keIelc]|g_1q۪aTy͢Kϙ*ls8`ps@MC?qόzcmV%bQ-)?;'Q V&I=/FOp{&W6 ?!)@!VøH#F# Ȟo4j)?g$ڒ*+q Id OCtOgCvx%EtSەX(9.|ɯG]sUf60C%3qϑ1:GG]S ͥLQNaMcY()\ 0Þ,զY I~rvP 헁ހ\뺾-yjuZװmsͫ4E5Ԥ~ϜpǡǎB@7ƌQ8JßqF26yU-gxIDn]ź97)cjp=FO/:/u]E̼/ro|K0zR,1Y??7^)lH8H_`Yj40!"/ۋQfT8Þ4.\_"\AUȆ;{o0 @{;@ѺDYe"cs؃E^ҶӢ>\y$kMMϗ( YJU(9 .{U|WsKUPNLyC_G gɺpTx c>N2\ D-@!LmgM E+$&01 ~p. AF:1\ZvPMb5]p$>ҾZAi\),n#fX4`'TGe`'rspj3F Ox7@ʄvͣ4 `u6`ζ "cDgzjNe"[4 CaGp~Cj4: ⤁od:j+TPB *TPB *TPB *V%tEXtdate:create2020-11-28T12:50:45+08:00#%tEXtdate:modify2020-11-28T12:50:45+08:00tEXtSoftwarewww.inkscape.org<IENDB`wstroke-2.2.1/icons/48x48/000077500000000000000000000000001472137506600151375ustar00rootroot00000000000000wstroke-2.2.1/icons/48x48/wstroke.png000066400000000000000000000050601472137506600173440ustar00rootroot00000000000000PNG  IHDR00WgAMA a cHRMz&u0`:pQ<bKGDC pHYs*0*0oN IDAThpT?vC P (  P"ةKQQa2v*:"XR+Xj@ (UT"$=- I`wxfv{ϯ{]Z_9)ZJK [/@Etnɑ.5ە,}P];;3%7T,&wQJ WS7/8@L}>9 }S5/0Y<˰vD\5{Pp"똲 s z^s5ӗrɞ8z>KJ"{% 0PS53dU e? k_CR!ֵxdV,WهPnXyu`C(#ˁd9nhg`޼W RZZeD'oi\¯]H2v}.'=*`%\Eq꽹%=G;?$k9pmԠҗIe}N-;#56G˧CI0fs'+''yp3tM$ k@ y,BHۦѓM\Td~}'W"TwF{=|gcmw7e @;c`>̟ uIJUqRc԰uP5bۣK/L`mQ-A)Zf{??VM @Mػ8A1N"܌3q'|ܠ;U]y  P U~>{~9$wf@;:7XU@1WUL2~)tS6{u=D}^O+@ ^yo#@e֙;F~ՀE7 {q8@ eAy#AצRxui  -"szd;Xph/CmOԀ:AylK˟#@^]Bz#}J7b59'X2ڬh`9.=!DupL8X'd3~da g-ݏʟ穙]Ʉƾ` Xˍ ر!h ?p'qq@\N#(g2r$ލhxf2|Piqgl:~'Tv5CHβiW QNuLjEXMe`$e g.їR%e i_E` n5.MhȶjDL޿Q nPI1IfVJTmX7)< s@'Q[A4+[ mBB4<+7,PѺpb4]-3j6^&:G0;\?e{IF q;Xqu?&ri9Z@t!IZ 8sR>1,8>v#¸8_ϜlT8z 9Aya'hSҠ5W8Ogl&'G\Jq7khv`5j*V4|#,±r>n@`fݬmxV!.\]7t*$*HBQcyNΟ (ɸ?B1,脺^,J^,@8r#6V|raEWuY9oDqS3?辯Rق&^'u6#ōݎNâ>EFnHյiW"`30 9|\y9;ct6p0K | ugpaPFd Vǘ6bڴqwC*G0 ^"Wl5B$=9qO7ҿjPG1jryQJMX'EjH-++[]5&v%$z؇؝x~9Z <߃i%+Pk@n/9 *)5,?#@AG8<ON{vن%tEXtdate:create2020-11-28T12:50:43+08:00$%tEXtdate:modify2020-11-28T12:50:43+08:00y/tEXtSoftwarewww.inkscape.org<IENDB`wstroke-2.2.1/icons/64x64/000077500000000000000000000000001472137506600151335ustar00rootroot00000000000000wstroke-2.2.1/icons/64x64/wstroke.png000066400000000000000000000067251472137506600173510ustar00rootroot00000000000000PNG  IHDR@@iqgAMA a cHRMz&u0`:pQ<bKGDC pHYs8@8@ IDATx{tՕ?y@"@ (U)*] RX׸M3 Pե 3 UD YQbDy($$~g$nE^+{9gﳿg|MI4M7,$|(j ư[;cPP&ⅺp#-"< y߶ :84uR;<;W9gaAo8pC˨*Ee6Kn^`nX]0Xk6#9@ ;@6X;X:?]B`hJMe q~~sznmG1V:SgY2CE_PƦlp5 ~Z!Jμ@FN3;y;o6* 2u b&񫻎JMO壺 ĭD|w lBdTO:Sߣ0VK'{ a,Db Qf hi|((莱ǩ2vlQl0iyǓ˭ F(%[MSz:)PfŊѥ7 FkʞN7ĉ|6͘M4#{0 /O;O.yn|,i7D_ eI0q k)o}p\5i89{bD{󝟌(O@#'Nj$x@Iq5_MǝXy/@^R %FjAx1)GZHS='< b+A:!FrC6zb9I"gҁa0' r!VQ|XP5?P1X!o=·yP P/ 00*P j:_vl9oM~`t~ݫ7HmT~w;Dt = FNmgj gB pt5dY7Q40BCsBϏkZ}&8>?u@%J=o 0Q\PY &yhh^#Pȩ7>JoI2Bz wSk@~#PsY|ei K٫#Ow9"M@w P>)=BnGMiP"mNO4 5K4i±M m h *4\ (F}^uKW}0E}.Gq)Rx)hOa?^ 5_}CaRGAAbo ~_jyeȖ2ЛSlgߢdߓCTv|M>(cÈ ܅va zJxDKA J{[)d]*94dz$EkG0:"hQ]B>w!$`syꆅ~ k1]m2~>'F/t0L}<9uBZRWǔ eHN!+@Ρ~IR6) 7l,wY6w=}I`T<4pvN8RZoizѲah\n5&͞<-Ge#PmkCy&&څ Ʋ;kiaD?:of{,bNOpDPltӲوn T哂Xx1Y=-|R%񈠸1&Ƃ^F)`߸uX:JKߗ'?LaIjM7@a#Qjyַ1껱q.wa+СGxU%xs z1D'2j+-ǾQ0) j)LIfGOgW(̦o#Â{wHMQ$xC5۰:7TYd_Ip&oxQDwwO֫1^_K܀le)Nc%+_>q45O !9 !89,$kC6x> KY>Qp]:'V"IFNڛ&?׃h"Iu dZ&ٞ5@4z`Q!8UND'Rx+_َC̀5YTPc_ہ:Y7xq= ܦaQT&ȟ s'*;\R=:V`)_nP`iNZoB:i9P\}_&c?C}p:I]gj QښO"Vx#t[ˡZB)ךWj`uTk*CҮ:5ss, [^EGQ{Y|9tI,!Q ˛ǚ`6| [A_! _Z`uR1 cedzԐkB >T8zjJQY/3Qy|@m4zkZێ}hS95S ̐PG1- U1 bMg}#@,Y׺KQ+z,ekj@QȃGBE~Љn PudoS%;P'kXQt/Ekc}aTF7^__~M}$OG0_4iǯ4D(]s0UvB R+zQe] '>C.7Y 50 K:)YNFe;KP hsnѴdG^)l: ʂ!C'TO ]RH&$)ln12%Ӫ=MQUO7ʖa %lq,/n[ m O6x`Fl%(E{O yn(*šV=^9uU^֐NcGoN^EkwB,^uw dž* fNZ&ݿ5Kӆ(pTcK-qDSr* M7L{7Dzkh+o^k nch&XrbkUD:w6`zl-jףRQvbk;w΃ ފ)@QQ7~7YG7ր@?mʮ \K|@yU.q* w/9+=EAR{Rkrv~ewahHc@j/rK^&R~WKUO$TLӮۦ]壜8>5Q1GQc*F͜;˾ HkFr. *~Sd1Au.ի"ՏTJsJalܖG@-%C+-xþAy%AEtyOGҷ֧z6Ir=b !`Cǥ,#K) ^5~ԹCr㞚S$HLBqy }4̯L)`Vt]ʰ5Hp2s >nlY RsD!V|;%%h'3-X@[DK%}B:_>)@;WIZT4O>[e~k8IY8 t`{&SkQ1AR a?81oDkĿ=?ؓ8~0l]>{ި3kQh秠2HG\/(h)w!fuU[akD07wG7 ;aqƃ:uoʸC iT3ͻڪWtb+c!_멚C+dlvlGO#s7hXCPs6'Mz $e6^U ]~퉘0@i58Px3 AF8CBC4WMBըS[PWYK"tp969$e`Y'Q3 zBw2W^SIT CyHd>aTS:/G#+JWԠaHc[1|ug[Q~~Goy0'<A%tEXtdate:create2020-11-28T12:50:44+08:00 (%tEXtdate:modify2020-11-28T12:50:44+08:00QސtEXtSoftwarewww.inkscape.org<IENDB`wstroke-2.2.1/icons/96x96/000077500000000000000000000000001472137506600151455ustar00rootroot00000000000000wstroke-2.2.1/icons/96x96/wstroke.png000066400000000000000000000125541472137506600173600ustar00rootroot00000000000000PNG  IHDR``w8gAMA a cHRMz&u0`:pQ<bKGDC pHYsT`T`;C9IIDATxyT?^/0b# .x0w(5*1g D F`bh0şK\ TaGf^ޭozgߘsgッnխwUa` @θ`n v7!Bv5]WQlLTVZlTOA,[`Kl,t4鷗cɩdLlnE8Y;Z\P@ee)եѴ<E򣧀 -ktKG o֣20+ډHU̹w-:VW6 G+x3~աzcpB.il1a7`g}6xӈ'0b]PYiQy5 '4 Gʖ~V8!"(TN=1C0i:ڀcW&p}^৭+net}ȣ#;e@7}s/\^ZU]p[;yuT,Cc 6,-ڈ+o0+Ke:]0 ;Gr_R#2 TVZTS-%'1{[ Vr\|_ `z@}hHJ!1+p s܆roʭT/6A%ˁP1*;8YW*Z 2'3]Eo,8w -vQۀȵ>㓢Pޙ#&ϙRvFqPYYm٬"IuXLW=_(.-,.$Ômޭ%7xIJq.mM[P^C1p =Tw,TG&mq`P,=YfЂ6!#;q,NF;0bAx9h!0laZ؋f8 ,^<{ 8sAARpއ,\Xp-b$+Qa>9uQArQC}2V)\vt (䳜XkɌt?g'ozmDq v\[4F9ςU<;XG(lg؎]%J P+0uqwLK JlΐB < .(rQtG (@qt.0x‰B&j_Yd$K=w%IˠX'jy )7+!j^^8K\*=Ta7=U!-۲v@8!qΑ(@aߌt |'b0e# pBj1 -R'Cۀt-g*ٍbPIo<O{/֋a#^Q";s>MÀ<8aO!uIzNsK-p0uo{$Jʲ`l0K<3:}7r'?0=c+_PK5VrW@n <o }XeXXN7mnL7{!I]ģ;Gܡ0,<'e#B'<@9CGcA#^2sojӜ1o}&/ZVP} aTcHp|(B|ccO~Tre'U< &slq"~IA_vSn`@ͱp+}*1of&`Znȓ>i$e*S֗7)ޝ,Td)m}.EY o慰3>PnA_V=*CH;r;1kɩh$;|UVl5އo]_VST _.jeG&4~MW^;#>(½)S\ӞyB" V׫ǜ若J(y禯c Ң>jHXPl.EI1):bf2Ou ^Ax=kna1v VPq5a4AG4IW}H=DqÛ12eR^Sqth7Cpf7uYѯ,۱ c; yoD)*oJV{ڎsO )h<}/h4;lǮE)AtؓCOSxmG cwBz] .u3@x>ļ&>̫ 2'w;& +K mP$l!l&uXL;(sP>1lfI͑x^0.2jHU@Ø qqc1(VS463Gj^>:0֧ ׎FLt:ylAb[`v606~lǽ`ى*Z!sv.}5'7'۰??| zXɅx}ϱc}T|@*??@LD{m6co WFcwJD]TADOEl՗E~IZ ύ;pr*:e-+,Ad @.lFͯzV_bk1#жc9[0.BYde]mUt+48z?M-[œ?[;c7JR 7b3F,0w=& image/svg+xml wstroke-2.2.1/icons/wstroke.svg000066400000000000000000000273051472137506600165660ustar00rootroot00000000000000 wstroke-2.2.1/input-inhibitor/000077500000000000000000000000001472137506600163515ustar00rootroot00000000000000wstroke-2.2.1/input-inhibitor/input_inhibitor.c000066400000000000000000000046311472137506600217270ustar00rootroot00000000000000/* * input_inhibitor.c -- inhibitor for grabbing key combinations * * Copyright 2020 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include #include #include static struct zwlr_input_inhibitor_v1* grab = NULL; static struct zwlr_input_inhibit_manager_v1* inhibitor = NULL; static void _add(G_GNUC_UNUSED void *data, struct wl_registry *registry, uint32_t name, const char *interface, G_GNUC_UNUSED uint32_t version) { if(strcmp(interface, zwlr_input_inhibit_manager_v1_interface.name) == 0) inhibitor = (struct zwlr_input_inhibit_manager_v1*)wl_registry_bind(registry, name, &zwlr_input_inhibit_manager_v1_interface, 1u); } static void _remove(G_GNUC_UNUSED void *data, G_GNUC_UNUSED struct wl_registry *registry, G_GNUC_UNUSED uint32_t name) { } static struct wl_registry_listener listener = { &_add, &_remove }; gboolean input_inhibitor_init() { struct wl_display* display = gdk_wayland_display_get_wl_display(gdk_display_get_default()); if(!display) return FALSE; struct wl_registry* registry = wl_display_get_registry(display); if(!registry) return FALSE; wl_registry_add_listener(registry, &listener, NULL); wl_display_dispatch(display); wl_display_roundtrip(display); if (!inhibitor) return FALSE; return TRUE; } gboolean input_inhibitor_grab() { if(!inhibitor) return FALSE; if(grab) return TRUE; grab = zwlr_input_inhibit_manager_v1_get_inhibitor(inhibitor); if(!grab) return FALSE; return TRUE; } void input_inhibitor_ungrab() { if(grab) { zwlr_input_inhibitor_v1_destroy(grab); wl_display_flush(gdk_wayland_display_get_wl_display(gdk_display_get_default())); grab = NULL; } } wstroke-2.2.1/input-inhibitor/input_inhibitor.h000066400000000000000000000023361472137506600217340ustar00rootroot00000000000000/* * input_inhibitor.h -- inhibitor for grabbing key combinations * * Copyright 2020 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #ifndef INPUT_INHIBITOR_H #define INPUT_INHIBITOR_H #include #ifdef __cplusplus extern "C" { #endif /* Try to initialize inhibitor (keyboard grabber) interface. */ gboolean input_inhibitor_init(); /* Try to grab keyboard. */ gboolean input_inhibitor_grab(); /* Release an existing grab. */ void input_inhibitor_ungrab(); #ifdef __cplusplus } #endif #endif wstroke-2.2.1/input-inhibitor/meson.build000066400000000000000000000014351472137506600205160ustar00rootroot00000000000000client_protocols = [ './wlr-input-inhibitor-unstable-v1.xml' ] wl_protos_client_src = [] wl_protos_headers = [] foreach p : client_protocols xml = join_paths(p) wl_protos_headers += wayland_scanner_client.process(xml) wl_protos_client_src += wayland_scanner_code.process(xml) endforeach lib_inhibitor_protos = static_library('wl_inhibitor_protos', wl_protos_client_src + wl_protos_headers, dependencies: [wayland_client]) # for the include directory protos = declare_dependency( link_with: lib_inhibitor_protos, sources: wl_protos_headers, ) input_inhibitor = static_library('input_inhibitor', 'input_inhibitor.c', dependencies: [wayland_client, protos, gdk]) input_inhibitor_dep = declare_dependency( link_with: input_inhibitor, include_directories: include_directories('.') ) wstroke-2.2.1/input-inhibitor/wlr-input-inhibitor-unstable-v1.xml000066400000000000000000000061241472137506600251630ustar00rootroot00000000000000 Copyright © 2018 Drew DeVault Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Clients can use this interface to prevent input events from being sent to any surfaces but its own, which is useful for example in lock screen software. It is assumed that access to this interface will be locked down to whitelisted clients by the compositor. Activates the input inhibitor. As long as the inhibitor is active, the compositor will not send input events to other clients. While this resource exists, input to clients other than the owner of the inhibitor resource will not receive input events. The client that owns this resource will receive all input events normally. The compositor will also disable all of its own input processing (such as keyboard shortcuts) while the inhibitor is active. The compositor may continue to send input events to selected clients, such as an on-screen keyboard (via the input-method protocol). Destroy the inhibitor and allow other clients to receive input. wstroke-2.2.1/meson.build000066400000000000000000000054551472137506600154000ustar00rootroot00000000000000project( 'wstroke', 'c', 'cpp', 'vala', version: '2.2.1', license: 'MIT', meson_version: '>=1.0.0', default_options: [ 'cpp_std=c++17', 'c_std=c11', 'warning_level=2', 'werror=false', ], ) # paths (only needed to install icon and desktop file) prefix = get_option('prefix') datadir = join_paths(prefix, get_option('datadir')) icon_dir = join_paths(datadir, 'icons') desktop_dir = join_paths(datadir, 'applications') # dependencies for loadable plugin boost = dependency('boost', modules: ['serialization'], static: false) wayfire = dependency('wayfire', version: '>=0.8.0') wayfire_deps_cmd = run_command('pkg-config', '--print-requires', 'wayfire', check: true) wayfire_deps = wayfire_deps_cmd.stdout().split('\n') if 'wlroots' in wayfire_deps message('Using wlroots version < 0.18') wlroots = dependency('wlroots') elif 'wlroots-0.18' in wayfire_deps message('Using wlroots version 0.18') wlroots = dependency('wlroots-0.18') else error('Cannot find supported wlroots dependency') endif wlroots_headers = wlroots.partial_dependency(includes: true, compile_args: true) wlserver = dependency('wayland-server') glibmm = dependency('glibmm-2.4') json = dependency('nlohmann_json', required: true) # additional dependencies for GUI gtkmm = dependency('gtkmm-3.0') gdkmm = dependency('gdkmm-3.0') glib = dependency('glib-2.0') gobject = dependency('gobject-2.0') gtk = dependency('gtk+-3.0') gdk = dependency('gdk-3.0') gnome = import('gnome') # filesystem library support # note: on Ubuntu 18.04 this only works with clang++ cpp = meson.get_compiler('cpp') if cpp.has_link_argument('-lc++fs') add_project_link_arguments(['-lc++fs'], language: 'cpp') elif cpp.has_link_argument('-lc++experimental') add_project_link_arguments(['-lc++experimental'], language: 'cpp') elif cpp.has_link_argument('-lstdc++fs') add_project_link_arguments(['-lstdc++fs'], language: 'cpp') endif # wayland-scanner -- needed for keyboard grabber and input inhibitor wayland_client = dependency('wayland-client') wayland_scanner = find_program('wayland-scanner') wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', arguments: ['private-code', '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( wayland_scanner, output: '@BASENAME@-client-protocol.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) add_project_arguments(['--vapidir', meson.current_source_dir() + '/src'], language: 'vala') add_project_arguments(['--pkg', 'input_inhibitor'], language: 'vala') subdir('input-inhibitor') subdir('toplevel-grabber') subdir('src') subdir('example') install_data('wstroke.xml', install_dir: wayfire.get_variable(pkgconfig: 'metadatadir')) install_data('wstroke-config.desktop', install_dir: desktop_dir) install_man('wstroke-config.1') subdir('icons') wstroke-2.2.1/src/000077500000000000000000000000001472137506600140145ustar00rootroot00000000000000wstroke-2.2.1/src/actiondb.cc000066400000000000000000000310061472137506600161060ustar00rootroot00000000000000/* * Copyright (c) 2008-2009, Thomas Jaeger * Copyright (c) 2020-2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "actiondb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ACTIONDB_CONVERT_CODES #include "convert_keycodes.h" static inline uint32_t convert_modifier(uint32_t mod) { return KeyCodes::convert_modifier(mod); } static inline uint32_t convert_keysym(uint32_t key) { return KeyCodes::convert_keysym(key); } #else static inline uint32_t convert_modifier(G_GNUC_UNUSED uint32_t mod) { throw std::runtime_error("unsupported action DB version!\nrun the wstroke-config program first to convert it to the new format\n"); } static inline uint32_t convert_keysym(G_GNUC_UNUSED uint32_t key) { throw std::runtime_error("unsupported action DB version!\nrun the wstroke-config program first to convert it to the new format\n"); } #endif BOOST_CLASS_EXPORT(Action) BOOST_CLASS_EXPORT(Command) BOOST_CLASS_EXPORT(ModAction) BOOST_CLASS_EXPORT(SendKey) BOOST_CLASS_EXPORT(SendText) BOOST_CLASS_EXPORT(Scroll) BOOST_CLASS_EXPORT(Ignore) BOOST_CLASS_EXPORT(Button) BOOST_CLASS_EXPORT(Misc) BOOST_CLASS_EXPORT(Global) BOOST_CLASS_EXPORT(View) BOOST_CLASS_EXPORT(Plugin) BOOST_CLASS_EXPORT(Touchpad) template void Action::serialize(G_GNUC_UNUSED Archive & ar, G_GNUC_UNUSED unsigned int version) {} template void Command::serialize(Archive & ar, unsigned int version) { ar & boost::serialization::base_object(*this); ar & cmd; if(version > 0) ar & desktop_file; } template void Plugin::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) { ar & boost::serialization::base_object(*this); ar & cmd; } template void ModAction::load(Archive & ar, G_GNUC_UNUSED unsigned int version) { ar & boost::serialization::base_object(*this); ar & mods; if (version < 1) mods = convert_modifier(mods); } template void ModAction::save(Archive & ar, G_GNUC_UNUSED unsigned int version) const { ar & boost::serialization::base_object(*this); ar & mods; } template void SendKey::load(Archive & ar, const unsigned int version) { ar & boost::serialization::base_object(*this); ar & key; if (version < 2) { uint32_t code; ar & code; if (version < 1) { bool xtest; ar & xtest; } key = convert_keysym(key); } } template void SendKey::save(Archive & ar, G_GNUC_UNUSED unsigned int version) const { ar & boost::serialization::base_object(*this); ar & key; } template void SendText::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) { ar & boost::serialization::base_object(*this); ar & text; } template void Scroll::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) { ar & boost::serialization::base_object(*this); } template void Ignore::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) { ar & boost::serialization::base_object(*this); } template void Button::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) { ar & boost::serialization::base_object(*this); ar & button; } template void Misc::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) { ar & boost::serialization::base_object(*this); ar & type; } std::unique_ptr Misc::convert() const { switch(type) { case SHOWHIDE: return Global::create(Global::Type::SHOW_CONFIG); case NONE: case DISABLE: case UNMINIMIZE: default: return Global::create(Global::Type::NONE); } } template void Global::load(Archive & ar, G_GNUC_UNUSED unsigned int version) { ar & boost::serialization::base_object(*this); ar & type; /* allow later extensions to add more types that might not be supported in older versions */ if((uint32_t)type >= n_actions) type = Type::NONE; } template void Global::save(Archive & ar, G_GNUC_UNUSED unsigned int version) const { ar & boost::serialization::base_object(*this); ar & type; } template void View::load(Archive & ar, G_GNUC_UNUSED unsigned int version) { ar & boost::serialization::base_object(*this); ar & type; /* allow later extensions to add more types that might not be supported in older versions */ if((uint32_t)type >= n_actions) type = Type::NONE; } template void View::save(Archive & ar, G_GNUC_UNUSED unsigned int version) const { ar & boost::serialization::base_object(*this); ar & type; } template void Touchpad::load(Archive & ar, G_GNUC_UNUSED unsigned int version) { ar & boost::serialization::base_object(*this); ar & type; /* allow later extensions to add more types that might not be supported in older versions */ if((uint32_t)type >= n_actions) type = Type::NONE; ar & fingers; } template void Touchpad::save(Archive & ar, G_GNUC_UNUSED unsigned int version) const { ar & boost::serialization::base_object(*this); ar & type; ar & fingers; } class StrokeSet : public std::set> { friend class boost::serialization::access; template void serialize(Archive & ar, const unsigned int version); }; BOOST_CLASS_EXPORT(StrokeSet) template void StrokeSet::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) { ar & boost::serialization::base_object > >(*this); } template void StrokeInfo::load(Archive & ar, const unsigned int version) { if (version >= 4) { ar & stroke; ar & action; } else { StrokeSet strokes; ar & strokes; if(strokes.size() && *strokes.begin()) stroke = std::move(**strokes.begin()); boost::shared_ptr action2; ar & action2; if(version < 2) { /* convert Misc actions to new types */ Misc* misc = dynamic_cast(action2.get()); if(misc) action = misc->convert(); } if(!action && version < 3) { /* convert Scroll and Text actions to Global / None -- they are not supported */ Scroll* scroll = dynamic_cast(action2.get()); if(scroll) action = Touchpad::create(Touchpad::Type::SCROLL, 2, scroll->get_mods()); else { SendText* text = dynamic_cast(action2.get()); if(text) action = Global::create(Global::Type::NONE); } } if(!action) action = action2->clone(); } if (version == 0) return; ar & name; } class Unique { friend class boost::serialization::access; template void serialize(Archive & ar, const unsigned int version); public: int level; /* not used */ int i; /* (not saved in the archive) */ }; template void Unique::serialize(G_GNUC_UNUSED Archive & ar, G_GNUC_UNUSED unsigned int version) {} template<> ActionListDiff* ActionListDiff::add_child(std::string name, bool app) { children.emplace_back(); ActionListDiff *child = &(children.back()); child->name = name; child->app = app; child->parent = this; child->level = level + 1; return child; } void ActionDB::convert_actionlist(ActionListDiff& dst, ActionListDiff& src, std::unordered_map& mapping, std::unordered_set& extra_unique) { for(Unique* x : src.order) { if(mapping.count(x)) throw std::runtime_error("Unique added multiple times!\n"); stroke_id z = get_next_id(); stroke_order.push_back(z); stroke_map[z] = std::pair(z, &dst); dst.order.push_back(z); mapping[x] = z; } for(Unique* x : src.deleted) { auto it = mapping.find(x); /* Note: due to how deletions are handled in earlier versions, * a Unique can end up staying in the deleted list of a child * even after it was deleted from the parent (this happens if it * is first deleted from the child and then the parent). In this * case, it is safe to just ignore it. */ if(it != mapping.end()) dst.deleted.insert(it->second); else extra_unique.insert(x); } for(auto& x : src.added) { auto it = mapping.find(x.first); if(it == mapping.end()) throw std::runtime_error("Unique not found!\n"); dst.added.insert(std::make_pair(it->second, std::move(x.second))); } for(auto& x : src.children) { ActionListDiff* y = dst.add_child(x.name, x.app); convert_actionlist(*y, x, mapping, extra_unique); } } template void ActionDB::load(Archive & ar, const unsigned int version) { if (version > 5) throw std::runtime_error("ActionDB::load(): unsupported archive version, maybe it was created with a newer version of WStroke?\n"); if (version == 5) { ar & root; ar & exclude_apps; if(next_id) { // read the order of strokes -- only matters for the GUI ar & stroke_order; ar & stroke_map; // recreate IDs mapping for(stroke_id x : stroke_order) if(x + 1 > next_id) next_id = x + 1; for(stroke_id x = 1; x < next_id; x++) if(!stroke_map.count(x)) available_ids.push_back(x); } } else if (version >= 2) { ActionListDiff root_tmp; ar & root_tmp; std::unordered_map mapping; std::unordered_set extra_unique; convert_actionlist(root, root_tmp, mapping, extra_unique); if (version >= 4) ar & exclude_apps; for(auto& x : mapping) delete x.first; for(Unique* x : extra_unique) delete x; } if (version == 1) { std::map strokes; ar & strokes; for (std::map::iterator i = strokes.begin(); i != strokes.end(); ++i) add_stroke(&root, std::move(i->second)); } if (version == 0) { std::map strokes; ar & strokes; for (std::map::iterator i = strokes.begin(); i != strokes.end(); ++i) { i->second.name = i->first; add_stroke(&root, std::move(i->second)); } } root.add_apps(apps); root.name = _("Default"); read_version = version; } char const * const ActionDB::wstroke_actions_versions[3] = { "actions-wstroke-2", "actions-wstroke", nullptr }; char const * const ActionDB::easystroke_actions_versions[5] = { "actions-0.5.6", "actions-0.4.1", "actions-0.4.0", "actions", nullptr }; bool ActionDB::read(const std::string& config_file_name, bool readonly) { clear(); next_id = readonly ? 0 : 1; if(!std::filesystem::exists(config_file_name)) return false; if(!std::filesystem::is_regular_file(config_file_name)) return false; std::ifstream ifs(config_file_name.c_str(), std::ios::binary); boost::archive::text_iarchive ia(ifs); ia >> *this; return true; } stroke_id ActionDB::add_stroke(ActionListDiff* parent, StrokeInfo&& si, stroke_id before) { stroke_id new_id = get_next_id(); parent->added.emplace(new_id, std::move(si)); unsigned int order = 0; auto it = stroke_order.end(); if(before) for(auto it = stroke_order.begin(); it != stroke_order.end(); ++it) if(*it == before) break; if(it != stroke_order.end()) order = stroke_map.at(*it).first; else if(!stroke_order.empty()) order = stroke_map.at(stroke_order.back()).first + 1; stroke_map[new_id] = std::pair(order, parent); it = stroke_order.insert(it, new_id); /* if the new element was inserted in the middle, the sort order * for all later elements needs to be updated */ for(++it, ++order; it != stroke_order.end(); ++it, ++order) { /* optimization: if the sort order is already large enough, we * do not need to update anymore (this can happen if strokes were * removed before */ auto& x = stroke_map.at(*it); if(x.first >= order) break; x.first = order; } return new_id; } wstroke-2.2.1/src/actiondb.h000066400000000000000000000546701472137506600157640ustar00rootroot00000000000000/* * Copyright (c) 2008-2009, Thomas Jaeger * Copyright (c) 2020-2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __STROKEDB_H__ #define __STROKEDB_H__ #include #include #include #include #include #include #include #include #include #include #include #include #include "gesture.h" class Action; class Command; class SendKey; class SendText; class Scroll; class Ignore; class Button; class Misc; class Global; class View; class Plugin; class Ranking; class Touchpad; class ActionVisitor { public: virtual ~ActionVisitor() { } virtual void visit(const Command* action) = 0; virtual void visit(const SendKey* action) = 0; virtual void visit(const SendText* action) = 0; virtual void visit(const Scroll* action) = 0; virtual void visit(const Ignore* action) = 0; virtual void visit(const Button* action) = 0; virtual void visit(const Global* action) = 0; virtual void visit(const View* action) = 0; virtual void visit(const Plugin* action) = 0; virtual void visit(const Touchpad* action) = 0; }; class Action { friend class boost::serialization::access; template void serialize(Archive & ar, const unsigned int version); public: virtual void visit(ActionVisitor* visitor) const = 0; virtual std::string get_type() const = 0; virtual ~Action() {} virtual std::unique_ptr clone() const = 0; }; class Command : public Action { friend class boost::serialization::access; template void serialize(Archive & ar, const unsigned int version); Command(const std::string &c, const std::string& fn) : cmd(c), desktop_file(fn) {} Command(const Command&) = default; public: std::string cmd; std::string desktop_file; // name of the .desktop file that belongs to this command Command() {} static std::unique_ptr create(const std::string& c, const std::string& fn = "") { return std::unique_ptr(new Command(c, fn)); } void visit(ActionVisitor* visitor) const override { visitor->visit(this); } std::string get_type() const override { return "Command"; } const std::string& get_cmd() const { return cmd; } std::unique_ptr clone() const override { return std::unique_ptr(new Command(*this)); } }; BOOST_CLASS_VERSION(Command, 1) class ModAction : public Action { friend class boost::serialization::access; BOOST_SERIALIZATION_SPLIT_MEMBER() template void load(Archive & ar, const unsigned int version); template void save(Archive & ar, const unsigned int version) const; protected: ModAction() {} uint32_t mods = 0; ModAction(uint32_t mods_) : mods(mods_) {} ModAction(const ModAction&) = default; public: uint32_t get_mods() const { return mods; } }; BOOST_CLASS_VERSION(ModAction, 1) /* version 1: save modifiers as enum wlr_keyboard_modifier * this notably does not support Gdk's "virtual" modifiers */ class SendKey : public ModAction { friend class boost::serialization::access; uint32_t key; BOOST_SERIALIZATION_SPLIT_MEMBER() template void load(Archive & ar, const unsigned int version); template void save(Archive & ar, const unsigned int version) const; SendKey(uint32_t key_, uint32_t mods) : ModAction(mods), key(key_) {} SendKey(const SendKey&) = default; public: SendKey() {} static std::unique_ptr create(uint32_t key, uint32_t mods) { return std::unique_ptr(new SendKey(key, mods)); } std::string get_type() const override { return "SendKey"; } uint32_t get_key() const { return key; } void visit(ActionVisitor* visitor) const override { visitor->visit(this); } std::unique_ptr clone() const override { return std::unique_ptr(new SendKey(*this)); } }; BOOST_CLASS_VERSION(SendKey, 2) /* version 2: save hardware keycode in key, omit separate code variable */ class SendText : public Action { friend class boost::serialization::access; std::string text; template void serialize(Archive & ar, const unsigned int version); SendText(Glib::ustring text_) : text(text_) {} SendText(const SendText&) = default; public: SendText() {} static std::unique_ptr create(Glib::ustring text) { return std::unique_ptr(new SendText(text)); } std::string get_type() const override { return "SendText"; } const Glib::ustring get_text() const { return text; } void visit(ActionVisitor* visitor) const override { visitor->visit(this); } std::unique_ptr clone() const override { return std::unique_ptr(new SendText(*this)); } }; class Scroll : public ModAction { friend class boost::serialization::access; template void serialize(Archive & ar, const unsigned int version); Scroll(uint32_t mods) : ModAction(mods) {} Scroll(const Scroll&) = default; public: Scroll() {} static std::unique_ptr create(uint32_t mods) { return std::unique_ptr(new Scroll(mods)); } std::string get_type() const override { return "Scroll"; } void visit(ActionVisitor* visitor) const override { visitor->visit(this); } std::unique_ptr clone() const override { return std::unique_ptr(new Scroll(*this)); } }; class Touchpad : public ModAction { friend class boost::serialization::access; public: enum Type { NONE, SCROLL, SWIPE, PINCH }; Type type = NONE; uint32_t fingers = 0; private: BOOST_SERIALIZATION_SPLIT_MEMBER() template void load(Archive & ar, const unsigned int version); template void save(Archive & ar, const unsigned int version) const; Touchpad(uint32_t mods, uint32_t fingers_, Type t) : ModAction(mods), type(t), fingers(fingers_) {} Touchpad(const Touchpad&) = default; public: Touchpad() {} static constexpr uint32_t n_actions = static_cast(Type::PINCH) + 1; static std::unique_ptr create(Type t, uint32_t fingers, uint32_t mods) { return std::unique_ptr(new Touchpad(mods, fingers, t)); } std::string get_type() const override { return "Touchpad"; } Type get_action_type() const { return type; } void visit(ActionVisitor* visitor) const override { visitor->visit(this); } std::unique_ptr clone() const override { return std::unique_ptr(new Touchpad(*this)); } }; class Ignore : public ModAction { friend class boost::serialization::access; template void serialize(Archive & ar, const unsigned int version); Ignore(uint32_t mods) : ModAction(mods) {} Ignore(const Ignore&) = default; public: Ignore() {} static std::unique_ptr create(uint32_t mods) { return std::unique_ptr(new Ignore(mods)); } std::string get_type() const override { return "Ignore"; } void visit(ActionVisitor* visitor) const override { visitor->visit(this); } std::unique_ptr clone() const override { return std::unique_ptr(new Ignore(*this)); } }; class Button : public ModAction { friend class boost::serialization::access; template void serialize(Archive & ar, const unsigned int version); Button(uint32_t mods, uint32_t button_) : ModAction(mods), button(button_) {} Button(const Button&) = default; uint32_t button = 0; public: Button() {} static std::unique_ptr create(uint32_t mods, uint32_t button_) { return std::unique_ptr(new Button(mods, button_)); } std::string get_type() const override { return "Button"; } uint32_t get_button() const { return button; } void visit(ActionVisitor* visitor) const override { visitor->visit(this); } std::unique_ptr clone() const override { return std::unique_ptr(new Button(*this)); } }; /* Misc action -- not used anymore, kept only for compatibility with old Easystroke config files */ class Misc : public Action { friend class boost::serialization::access; public: enum Type { NONE, UNMINIMIZE, SHOWHIDE, DISABLE }; Type type; private: template void serialize(Archive & ar, const unsigned int version); Misc(Type t) : type(t) {} public: Misc() {} std::string get_type() const override { return "Misc"; } static std::unique_ptr create(Type t) { return std::unique_ptr(new Misc(t)); } /* does nothing */ void visit(G_GNUC_UNUSED ActionVisitor* visitor) const override { return; } /* convert old Misc actions to new representation */ std::unique_ptr convert() const; std::unique_ptr clone() const override { return std::unique_ptr(new Misc(*this)); } }; /* new version: instead of Misc, we have Global Actions, View Actions, and Custom Plugin */ class Global : public Action { friend class boost::serialization::access; public: enum class Type : uint32_t { NONE, EXPO, SCALE, SCALE_ALL, SHOW_CONFIG, SHOW_DESKTOP, CUBE }; protected: Type type; BOOST_SERIALIZATION_SPLIT_MEMBER() template void load(Archive & ar, const unsigned int version); template void save(Archive & ar, const unsigned int version) const; Global(Type t): type(t) { } Global(): type(Type::NONE) { } Global(const Global&) = default; public: static constexpr uint32_t n_actions = static_cast(Type::CUBE) + 1; static const char* types[n_actions]; static const char* get_type_str(Type type); std::string get_type() const override { return "Global Action"; } static std::unique_ptr create(Type t) { return std::unique_ptr(new Global(t)); } Type get_action_type() const { return type; } void visit(ActionVisitor* visitor) const override { visitor->visit(this); } std::unique_ptr clone() const override { return std::unique_ptr(new Global(*this)); } }; /* actions performed on the active view (either directly or via another plugin) */ class View : public Action { friend class boost::serialization::access; public: enum class Type : uint32_t { NONE, CLOSE, MAXIMIZE, MOVE, RESIZE, MINIMIZE, FULLSCREEN, SEND_TO_BACK, ALWAYS_ON_TOP, STICKY }; protected: Type type; BOOST_SERIALIZATION_SPLIT_MEMBER() template void load(Archive & ar, const unsigned int version); template void save(Archive & ar, const unsigned int version) const; View(Type t): type(t) { } View(): type(Type::NONE) { } View(const View&) = default; public: static constexpr uint32_t n_actions = static_cast(Type::STICKY) + 1; static const char* types[n_actions]; static const char* get_type_str(Type type); std::string get_type() const override { return "View Action"; } static std::unique_ptr create(Type t) { return std::unique_ptr(new View(t)); } Type get_action_type() const { return type; } void visit(ActionVisitor* visitor) const override { visitor->visit(this); } std::unique_ptr clone() const override { return std::unique_ptr(new View(*this)); } }; /* custom plugin activator */ class Plugin : public Action { friend class boost::serialization::access; protected: template void serialize(Archive & ar, const unsigned int version); std::string cmd; Plugin() {} Plugin(const std::string &c) : cmd(c) {} Plugin(const Plugin&) = default; public: static std::unique_ptr create(const std::string &c) { return std::unique_ptr(new Plugin(c)); } void visit(ActionVisitor* visitor) const override { visitor->visit(this); } std::string get_type() const override { return "Custom Plugin Action"; } const std::string& get_action() const { return cmd; } std::unique_ptr clone() const override { return std::unique_ptr(new Plugin(*this)); } }; class StrokeInfo { private: friend class ActionDB; template friend class ActionListDiff; friend class boost::serialization::access; BOOST_SERIALIZATION_SPLIT_MEMBER() template void load(Archive & ar, const unsigned int version); template void save(Archive & ar, const unsigned int version) const; public: StrokeInfo(std::unique_ptr&& a) : action(std::move(a)) { } StrokeInfo() {} std::unique_ptr action; Stroke stroke; std::string name; }; BOOST_CLASS_VERSION(StrokeInfo, 4) struct StrokeRow { const Stroke* stroke = nullptr; const std::string* name = nullptr; const Action* action = nullptr; bool deleted = false; bool stroke_overwrite = false; bool name_overwrite = false; bool action_overwrite = false; }; class Ranking { int x, y; public: const Stroke *stroke, *best_stroke; Action* action; double score; std::string name; std::multimap > r; }; typedef uint32_t stroke_id; class Unique; class ActionDB; template class ActionListDiff { private: friend class boost::serialization::access; friend class ActionDB; using unique_t = typename std::conditional::type; template void serialize(Archive & ar, const unsigned int version) { ar & deleted; ar & added; ar & name; ar & children; ar & app; if constexpr (!uptr) { ar & parent; return; } if (version == 0) return; ar & order; } ActionListDiff *parent = nullptr; std::set deleted; std::map added; std::list order; // only for old version (uptr == true) std::list children; void remove(unique_t id, bool really, ActionListDiff* skip = nullptr); public: int level = 0; bool app = false; std::string name; typedef typename std::list::iterator iterator; iterator begin() { return children.begin(); } iterator end() { return children.end(); } ActionListDiff *get_parent() { return parent; } StrokeRow get_info(unique_t id, bool need_attr = true) const; const std::string& get_stroke_name(unique_t id) const; Action* get_stroke_action(unique_t id) const { auto it = added.find(id); if(it != added.end() && it->second.action) return it->second.action.get(); //!! TODO: check for non-null parent ?? return parent->get_stroke_action(id); } bool has_stroke(unique_t id) const { auto it = added.find(id); if(it != added.end() && !it->second.stroke.trivial()) return true; return false; } int size_rec() const { int size = added.size(); for (auto i = children.begin(); i != children.end(); i++) size += i->size_rec(); return size; } bool resettable(unique_t id) const { return parent && (added.count(id) || deleted.count(id)) && parent->contains(id); } void set_action(unique_t id, std::unique_ptr&& action) { added[id].action = std::move(action); } void set_stroke(unique_t id, Stroke&& stroke) { added[id].stroke = std::move(stroke); } void set_name(unique_t id, std::string name) { added[id].name = std::move(name); } bool contains(unique_t id) const { if (deleted.count(id)) return false; if (added.count(id)) return true; return parent && parent->contains(id); } void reset(unique_t id); void add_apps(std::map &apps) { if (app) apps[name] = this; for (auto& x : children) x.add_apps(apps); } ActionListDiff *add_child(std::string name, bool app); std::map get_strokes() const; std::set get_ids(bool include_deleted) const; int count_actions() const { if(parent) return get_ids(false).size(); else return added.size(); } Action* handle(const Stroke& s, Ranking* r) const; template void visit_all_actions(CB&& cb) const { for(const auto& x : added) if(x.second.action) cb(x.second.action.get()); } }; BOOST_CLASS_VERSION(ActionListDiff, 1) BOOST_CLASS_VERSION(ActionListDiff, 1) class ActionDB { private: /* input / output via boost */ friend class boost::serialization::access; friend class ActionListDiff; template void load(Archive & ar, const unsigned int version); template void save(Archive & ar, const unsigned int version) const; BOOST_SERIALIZATION_SPLIT_MEMBER() /* Recursively convert an ActionListDiff tree from an older version. * This can be called from the load() function above. */ void convert_actionlist(ActionListDiff& dst, ActionListDiff& src, std::unordered_map& mapping, std::unordered_set& extra_unique); unsigned int read_version = 0; /* Main storage */ std::map*> apps; ActionListDiff root; std::unordered_set exclude_apps; /* Storage of stroke_ids. * We store all stroke_ids in the order they should appear in the * gesture list along with a mapping from stroke_id to their sort order. */ std::list stroke_order; /* Each stroke_id has an "owner", that is the ActionListDiff where it was added. */ std::unordered_map*>> stroke_map; /* Next available ID. Setting this to 0 means no strokes can be added. */ stroke_id next_id = 1; /* Available (previously deleted) stroke IDs that can be reused. */ std::vector available_ids; /* Get a new stroke ID (for adding a new stroke). */ stroke_id get_next_id() { if(!next_id) throw std::runtime_error("ActionDB: read-only database!\n"); stroke_id ret = next_id; if(available_ids.size()) { ret = available_ids.back(); available_ids.pop_back(); } else next_id++; return ret; } /* Remove a stroke ID that is no longer used. */ void free_id(stroke_id id) { if(id >= next_id) throw std::runtime_error("ActionDB: too large ID to remove!\n"); if(id + 1 == next_id) next_id--; else available_ids.push_back(id); } /* Remove a set of strokes from the stroke_order list. Uses a sort * if the number of strokes is large to avoid quadratic runtime. */ template void remove_strokes_helper(iter&& begin, iter&& end); template void remove_strokes_from_order(iter begin, iter end, bool readonly = false); /* Helper to remove an app. */ void remove_app_r(ActionListDiff* app); /* Helper for merging two ActionDBs. */ void merge_actions_r(ActionListDiff* dst, ActionListDiff* src, std::unordered_map& id_map); /* Needed for clear(). */ ActionDB(ActionDB&&) = default; ActionDB& operator = (ActionDB&&) = default; public: /****************************************************************** * Input / output */ /* Try to read actions from the given config file. Returns false if * no config file found, throws an exception on other errors. * Note: this will clear any existing actions first. */ bool read(const std::string& config_file_name, bool readonly = false); /* Try to save actions to the config file; throws exception on failure. */ void write(const std::string& config_file_name) const; /* During read(), the version of the archive is stored. It can be retrieved here * and used to decide if a conversion from an older took place during loading. */ unsigned int get_read_version() const { return read_version; } /* Merge or replace the contents of this ActionDB with the given other one. */ void merge_actions(ActionDB&& other); void overwrite_actions(ActionDB&& other); /****************************************************************** * Handling apps and groups of apps */ const ActionListDiff *get_action_list(const std::string& wm_class) const { auto i = apps.find(wm_class); return i == apps.end() ? nullptr : i->second; } const ActionListDiff *get_root() const { return &root; } ActionListDiff *get_root() { return &root; } /* Add a new ActionListDiff corresponding to the given name with * the given parent. * Preconditions: parent must belong to this ActionDB instance and * name must not have previously been added. */ ActionListDiff* add_app(ActionListDiff* parent, const std::string& name, bool real_app); /* Remove an app or group that belongs to this ActionDB and is not the root. */ void remove_app(ActionListDiff* app); /* Manage apps that are excluded. */ const std::unordered_set& get_exclude_apps() const { return exclude_apps; } bool exclude_app(const std::string& cl) const { return exclude_apps.count(cl); } bool add_exclude_app(const std::string& cl) { return exclude_apps.insert(cl).second; } bool remove_exclude_app(const std::string& cl) { return exclude_apps.erase(cl); } /****************************************************************** * Manage strokes. */ const ActionListDiff* get_stroke_owner(stroke_id id) const { return stroke_map.at(id).second; } unsigned int get_stroke_order(stroke_id id) const { return stroke_map.at(id).first; } unsigned int count_owned_strokes(const ActionListDiff* parent) const; /* Add a new stroke owned by the ActionList given as parent. */ stroke_id add_stroke(ActionListDiff* parent, StrokeInfo&& si, stroke_id before = 0); /* Remove a set of strokes together (avoiding quadratic runtime). */ template void remove_strokes(ActionListDiff* parent, it&& begin, it&& end); /* Remove or disable the given stroke from the ActionList given. * If the stroke is owned by this ActionList, it is deleted recursively; * otherwise, it is only disabled. */ void remove_stroke(ActionListDiff* parent, stroke_id id); /* move one stroke, changing the ordering of strokes */ void move_stroke(stroke_id id, stroke_id before, bool after); /* move a set of strokes from their position to be before dst */ template void move_strokes(it&& begin, it&& end, stroke_id before, bool after); /* Move or copy strokes between apps / groups. Returns if the stroke * was removed from src. */ bool move_stroke_to_app(ActionListDiff* src, ActionListDiff* dst, stroke_id id); void clear() { *this = ActionDB(); } ActionDB() { root.name = _("Default"); } /* Config file names */ static char const * const wstroke_actions_versions[3]; static char const * const easystroke_actions_versions[5]; }; BOOST_CLASS_VERSION(ActionDB, 5) #endif wstroke-2.2.1/src/actiondb_config.cc000066400000000000000000000405541472137506600174430ustar00rootroot00000000000000/* * Copyright (c) 2008-2009, Thomas Jaeger * Copyright (c) 2020-2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "actiondb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include template void StrokeInfo::save(Archive & ar, G_GNUC_UNUSED const unsigned int version) const { ar & stroke; ar & action; ar & name; } template void ActionDB::save(Archive & ar, G_GNUC_UNUSED unsigned int version) const { ar & root; ar & exclude_apps; ar & stroke_order; ar & stroke_map; } void ActionDB::write(const std::string& config_file_name) const { if(!next_id) throw std::runtime_error("ActionDB::write(): missing information!\n"); std::string tmp = config_file_name + ".tmp"; std::ofstream ofs(tmp.c_str()); boost::archive::text_oarchive oa(ofs); oa << *this; ofs.close(); if (rename(tmp.c_str(), config_file_name.c_str())) throw std::runtime_error(_("rename() failed")); printf("Saved actions.\n"); } template<> void ActionListDiff::remove(unique_t id, bool really, ActionListDiff* skip) { if(!really) deleted.insert(id); else deleted.erase(id); added.erase(id); for(auto& c : children) if(&c != skip) c.remove(id, true); } template<> bool ActionListDiff::has_stroke(unique_t id) const { auto it = added.find(id); if(it != added.end() && !it->second.stroke.trivial()) return true; return false; } template<> std::set ActionListDiff::get_ids(bool include_deleted) const { std::set ids = parent ? parent->get_ids(false) : std::set(); if(!include_deleted) for(const auto& x : deleted) ids.erase(x); for(const auto& x : added) ids.insert(x.first); return ids; } template<> StrokeRow ActionListDiff::get_info(stroke_id id, bool need_attr) const { StrokeRow si = parent ? parent->get_info(id, false) : StrokeRow(); if(need_attr) si.deleted = this->deleted.count(id); auto i = added.find(id); if(i == added.end()) return si; if(!parent || i->second.name != "") { si.name = &(i->second.name); if(need_attr) si.name_overwrite = (parent != nullptr); } if(!parent || !i->second.stroke.trivial()) { si.stroke = &i->second.stroke; if(need_attr) si.stroke_overwrite = (parent != nullptr); } if(i->second.action) { si.action = i->second.action.get(); if(need_attr) si.action_overwrite = (parent != nullptr); } return si; } template<> int ActionListDiff::size_rec() const { int size = added.size(); for (auto i = children.begin(); i != children.end(); i++) size += i->size_rec(); return size; } template<> void ActionListDiff::reset(unique_t id) { if(!parent) return; added.erase(id); deleted.erase(id); } template void ActionDB::remove_strokes_helper(iter&& begin, iter&& end) { std::sort(begin, end, [this](stroke_id x, stroke_id y) { return stroke_map.at(x).first < stroke_map.at(y).first; }); auto it = stroke_order.begin(); for(; begin != end; ++begin) { stroke_id x = *begin; while(true) { if(it == stroke_order.end()) throw std::runtime_error("ActionDB::remove_strokes_from_order(): missing stroke!\n"); if(*it == x) break; ++it; } it = stroke_order.erase(it); } } template void ActionDB::remove_strokes_from_order(iter begin, iter end, bool readonly) { if(begin == end) return; if(end - begin > 20) { if(readonly) { std::vector tmp; tmp.insert(tmp.end(), begin, end); remove_strokes_helper(tmp.begin(), tmp.end()); } else remove_strokes_helper(begin, end); } else for(auto it = stroke_order.begin(); it != stroke_order.end(); ) if(std::find(begin, end, *it) != end) it = stroke_order.erase(it); else ++it; } void ActionDB::remove_app_r(ActionListDiff* app) { /* 1. Remove all stroke_ids that are owned by app */ for(auto it = stroke_order.begin(); it != stroke_order.end(); ) { auto owner = stroke_map.at(*it).second; if(owner == app) { /* Remove this ID */ free_id(*it); stroke_map.erase(*it); it = stroke_order.erase(it); } else ++it; } /* 2. Remove from the list of apps */ if(app->app) apps.erase(app->name); /* 3. Remove all child apps */ for(auto& c : app->children) remove_app(&c); } ActionListDiff* ActionDB::add_app(ActionListDiff* parent, const std::string& name, bool real_app) { auto ret = parent->add_child(name, real_app); apps[name] = ret; return ret; } void ActionDB::remove_app(ActionListDiff* app) { /* Recursively remove all stroke_ids from this subtree and from the apps map */ remove_app_r(app); /* Remove the app from its parent -- this will recursively free memory */ auto parent = app->parent; /* parent is not null as app != root */ for(auto it = parent->children.begin(); it != parent->children.end(); ++it) if(&*it == app) { parent->children.erase(it); return; } throw std::runtime_error("ActionDB::remove_app(): app not found!\n"); } unsigned int ActionDB::count_owned_strokes(const ActionListDiff* parent) const { unsigned int ret = 0; for(const auto& x : parent->added) if(stroke_map.at(x.first).second == parent) ret++; return ret; } template void ActionDB::remove_strokes(ActionListDiff* parent, it&& begin, it&& end) { std::vector to_delete; end = std::remove_if(begin, end, [this, parent](stroke_id id) { bool really = (stroke_map.at(id).second == parent); parent->remove(id, really); return !really; }); remove_strokes_from_order(begin, end); for(; begin != end; ++begin) { stroke_id x = *begin; stroke_map.erase(x); free_id(x); } } template void ActionDB::remove_strokes::iterator>(ActionListDiff* parent, std::vector::iterator&& begin, std::vector::iterator&& end); void ActionDB::remove_stroke(ActionListDiff* parent, stroke_id id) { std::array tmp = {id}; remove_strokes(parent, tmp.begin(), tmp.end()); } void ActionDB::move_stroke(stroke_id id, stroke_id before, bool after) { if(id == before) return; std::list::iterator src = stroke_order.end(); std::list::iterator dst = stroke_order.end(); for(auto it = stroke_order.begin(); it != stroke_order.end(); ++it) { if(*it == id) src = it; if(*it == before) dst = it; } if(after && dst != stroke_order.end()) ++dst; if(src == stroke_order.end()) throw std::runtime_error("ActionDB::move_stroke(): stroke ID not found!\n"); src = stroke_order.erase(src); unsigned int order = 0; if(dst == stroke_order.end()) { if(!stroke_order.empty()) order = stroke_map.at(stroke_order.back()).first + 1; } else order = stroke_map.at(*dst).first; stroke_map.at(id).first = order; stroke_order.insert(dst, id); for(++order; dst != stroke_order.end(); ++dst, ++order) { auto& x = stroke_map.at(*dst); if(x.first >= order) break; x.first = order; } } template void ActionDB::move_strokes(it&& begin, it&& end, stroke_id before, bool after) { remove_strokes_from_order(begin, end, true); // note: we need to keep the order auto dst = stroke_order.end(); for(auto it2 = stroke_order.begin(); it2 != stroke_order.end(); ++it2) if(*it2 == before) { dst = it2; break; } if(after && dst != stroke_order.end()) ++dst; stroke_order.insert(dst, begin, end); /* recalculate the sort order for all elements */ unsigned int order = 0; for(stroke_id id : stroke_order) stroke_map.at(id).first = order++; } template void ActionDB::move_strokes::iterator>(std::vector::iterator&& begin, std::vector::iterator&& end, stroke_id before, bool after); template void ActionDB::move_strokes::reverse_iterator>(std::vector::reverse_iterator&& begin, std::vector::reverse_iterator&& end, stroke_id before, bool after); bool ActionDB::move_stroke_to_app(ActionListDiff* src, ActionListDiff* dst, stroke_id id) { if(src == dst) return false; if(!src->contains(id)) return false; /* Main cases: * 1. src is the ancestor of dst or vice versa -> we move all information from src to dst * (note: this might affect other descendants of src, and also potentially overwrites any * information already at dst) * 2. src and dst are "independent" -> in this case, we make a copy (potentially overwriting * any information in dst) * In either cases, the goal is to make dst look exactly like src was. */ auto parent = src->parent; while(parent && parent != dst) parent = parent->parent; if(parent == dst) { /* dst is the parent of src */ if(stroke_map.at(id).second == src) { /* simple case, just move everything */ dst->added[id] = std::move(src->added.at(id)); src->added.erase(id); stroke_map.at(id).second = dst; } else { /* move any properties that are overridden in src or in any of its parents */ StrokeInfo info; auto tmp = src; bool change_owner = false; do { auto it = tmp->added.find(id); if(it != tmp->added.end()) { if(stroke_map.at(id).second == tmp) change_owner = true; bool erase = true; if(!it->second.stroke.trivial()) { if(info.stroke.trivial()) info.stroke = std::move(it->second.stroke); else erase = false; } if(it->second.name != "") { if(info.name == "") info.name = std::move(it->second.name); else erase = false; } if(it->second.action) { if(!info.action) info.action = std::move(it->second.action); else erase = false; } if(erase) tmp->added.erase(it); } tmp = tmp->parent; } while(tmp != dst); StrokeInfo& di = dst->added[id]; // this might add a new element to dst->added if(!info.stroke.trivial()) di.stroke = std::move(info.stroke); if(info.name != "") di.name = std::move(info.name); if(info.action) di.action = std::move(info.action); if(change_owner) stroke_map.at(id).second = dst; } return false; } parent = dst->parent; while(parent && parent != src) parent = parent->parent; if(parent == src) { /* src is the parent of dst */ if(stroke_map.at(id).second == src) { /* simple case, just move down everything */ dst->added[id] = std::move(src->added.at(id)); src->remove(id, true, dst); stroke_map.at(id).second = dst; return true; } else { /* check if the stroke is deleted in any of dst's ancestors * (if yes, we will copy this stroke) */ bool deleted = false; auto tmp = dst->parent; while(tmp != src) if(tmp->deleted.count(id)) { deleted = true; break; } if(!deleted) { StrokeRow r = src->get_info(id, false); StrokeInfo& info = dst->added[id]; auto it = src->added.find(id); if(it != src->added.end()) { info = std::move(it->second); src->added.erase(it); } // we need to copy all things from r that are // (1) not already in info; and (2) overridden between src and dst bool copy_stroke = false; bool copy_name = false; bool copy_action = false; tmp = dst->parent; while(tmp != src) { auto it2 = tmp->added.find(id); if(!it2->second.stroke.trivial()) copy_stroke = true; if(it2->second.name != "") copy_name = true; if(it2->second.action) copy_action = true; } if(copy_stroke && info.stroke.trivial()) info.stroke = r.stroke->clone(); if(copy_name && info.name == "") info.name = *r.name; if(copy_action && !info.action) info.action = r.action->clone(); dst->deleted.erase(id); return false; } } } /* Here, dst and src are "unrelated": we need to copy all the info or * the whole stroke to dst */ dst->deleted.erase(id); if(dst->contains(id)) { /* This ID is already present, we need to copy parts of the info which * are not already the same. */ StrokeRow rsrc = src->get_info(id, false); StrokeRow rdst = dst->get_info(id, false); StrokeInfo* info = nullptr; /* Note: members of rsrc and rdst are pointers to the actual objects * which contain the relevant info; these can be used to compare if * anything needs to be copied. */ if(rsrc.stroke != rdst.stroke) { if(!info) info = &(dst->added[id]); info->stroke = rsrc.stroke->clone(); } if(rsrc.name != rdst.name) { if(!info) info = &(dst->added[id]); info->name = *rsrc.name; } if(rsrc.action != rdst.action) { if(!info) info = &(dst->added[id]); info->action = rsrc.action->clone(); } } else { // we copy the whole stroke StrokeRow r = src->get_info(id, false); StrokeInfo info; info.name = *r.name; info.action = r.action->clone(); info.stroke = r.stroke->clone(); add_stroke(dst, std::move(info)); } return false; } void ActionDB::merge_actions_r(ActionListDiff* dst, ActionListDiff* src, std::unordered_map& id_map) { auto new_dst = add_app(dst, src->name, src->app); for(auto& x : src->added) { auto it = id_map.find(x.first); if(it != id_map.end()) { new_dst->added[it->second] = std::move(x.second); } else { // note: this stroke should be owned by src in this case id_map[x.first] = add_stroke(new_dst, std::move(x.second)); /* StrokeRow r = src->get_info(x.first); StrokeInfo info; if(r.name) info.name = *r.name; if(r.action) info.action = r.action->clone(); if(r.stroke) info.stroke = r.stroke->clone(); add_stroke(action_list, std::move(info)); */ } } for(auto x : src->deleted) new_dst->deleted.insert(id_map.at(x)); for(auto& x : src->children) merge_actions_r(new_dst, &x, id_map); } void ActionDB::merge_actions(ActionDB&& other) { std::unordered_map id_map; for(const auto& x : other.exclude_apps) exclude_apps.insert(x); for(auto& x : other.root.added) id_map[x.first] = add_stroke(&root, std::move(x.second)); /* First we copy apps that exist in the current actions * (we don't try to merge groups since the tree structure can differ) */ auto other_apps = std::move(other.apps); // hack to avoid erasing while iterating (this works, since only erase() is called on other.apps) for(auto& x : other_apps) { auto it = apps.find(x.first); if(it == apps.end()) continue; auto action_list = it->second; /* Instead of only considering the apps added in this node, * we consider all strokes, since some properties can be overriden * on higher level */ for(auto id : other.stroke_order) { if(x.second->contains(id)) { StrokeRow r = x.second->get_info(id); if(! (r.stroke || r.name || r.action) ) continue; // no info (already copied in root) auto it2 = id_map.find(id); if(it2 != id_map.end()) { /* note: we use clone, since this might be an inherited stroke */ if(r.stroke) action_list->set_stroke(it2->second, r.stroke->clone()); if(r.name) action_list->set_name(it2->second, *r.name); if(r.action) action_list->set_action(it2->second, r.action->clone()); } else { /* copy this stroke */ StrokeInfo info; if(r.name) info.name = *r.name; if(r.action) info.action = r.action->clone(); if(r.stroke) info.stroke = r.stroke->clone(); add_stroke(action_list, std::move(info)); } } } other.remove_app(x.second); } /* copy the remaining tree structure */ for(auto& x : other.root.children) merge_actions_r(&root, &x, id_map); } void ActionDB::overwrite_actions(ActionDB&& other) { *this = std::move(other); for(auto& c : root.children) c.parent = &root; for(auto& x : stroke_map) if(x.second.second == &other.root) x.second.second = &root; } wstroke-2.2.1/src/actiondb_plugin.cc000066400000000000000000000045121472137506600174660ustar00rootroot00000000000000/* * Copyright (c) 2008-2009, Thomas Jaeger * Copyright (c) 2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "gesture.h" #include "actiondb.h" template<> const std::string& ActionListDiff::get_stroke_name(unique_t id) const { auto it = added.find(id); if(it != added.end() && it->second.name != "") return it->second.name; //!! TODO: check for non-null parent ?? return parent->get_stroke_name(id); } template<> std::map ActionListDiff::get_strokes() const { std::map strokes = parent ? parent->get_strokes() : std::map(); for(const auto& x : deleted) strokes.erase(x); for(const auto& x : added) if(!x.second.stroke.trivial()) strokes[x.first] = &x.second.stroke; return strokes; } template<> Action* ActionListDiff::handle(const Stroke& s, Ranking* r) const { double best_score = 0.0; Action* ret = nullptr; if(r) r->stroke = &s; const auto strokes = get_strokes(); for(const auto& x : strokes) { const Stroke& y = *x.second; double score; int match = Stroke::compare(s, y, score); if (match < 0) continue; bool new_best = false; if(score > best_score) { new_best = true; best_score = score; ret = get_stroke_action(x.first); if(r) r->best_stroke = &y; } if(r) { const std::string& name = get_stroke_name(x.first); r->r.insert(std::pair > (score, std::pair(name, &y))); if(new_best) r->name = name; } } if(r) { r->score = best_score; r->action = ret; } return ret; } wstroke-2.2.1/src/actions.cc000066400000000000000000001734671472137506600160050ustar00rootroot00000000000000/* * Copyright (c) 2008-2009, Thomas Jaeger * Copyright (c) 2020-2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "actions.h" #include "actiondb.h" #include "stroke_draw.h" #include "convert_keycodes.h" #include "input_inhibitor.h" #include #include #include #include #include #include #include #include "cellrenderertextish.h" #include "stroke_drawing_area.h" #include "config.h" #include class SelectButton { public: SelectButton(const Button& bt, Glib::RefPtr& widgets_); ~SelectButton(); bool run(); uint32_t button = 0; uint32_t state = 0; private: Gtk::MessageDialog *dialog; bool on_button_press(GdkEventButton *ev); Gtk::EventBox *eventbox; Gtk::ToggleButton *toggle_shift, *toggle_control, *toggle_alt, *toggle_super; Gtk::ComboBoxText *select_button; sigc::connection handler; Glib::RefPtr widgets; }; class SelectTouchpad { public: SelectTouchpad(const Touchpad& bt, Glib::RefPtr& widgets_, const Glib::ustring& action_name); ~SelectTouchpad() { } bool run(); uint32_t get_fingers() const; Touchpad::Type get_type() const; uint32_t state = 0; private: Gtk::Dialog *dialog; Gtk::ToggleButton *toggle_shift, *toggle_control, *toggle_alt, *toggle_super; Gtk::RadioButton *radio_scroll, *radio_pinch, *radio_swipe; Gtk::SpinButton *spin_fingers; Gtk::Adjustment *adj_fingers; Glib::RefPtr widgets; }; bool TreeViewMulti::on_button_press_event(GdkEventButton* event) { int cell_x, cell_y; Gtk::TreeViewColumn *column; pending = (get_path_at_pos(event->x, event->y, path, column, cell_x, cell_y)) && (get_selection()->is_selected(path)) && !(event->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK)); return Gtk::TreeView::on_button_press_event(event); } bool TreeViewMulti::on_button_release_event(GdkEventButton* event) { if (pending) { pending = false; get_selection()->unselect_all(); get_selection()->select(path); } return Gtk::TreeView::on_button_release_event(event); } void TreeViewMulti::on_drag_begin(const Glib::RefPtr &context) { pending = false; if (get_selection()->count_selected_rows() <= 1) return Gtk::TreeView::on_drag_begin(context); Glib::RefPtr pb = render_icon_pixbuf(Gtk::Stock::DND_MULTIPLE, Gtk::ICON_SIZE_DND); context->set_icon(pb, pb->get_width(), pb->get_height()); } TreeViewMulti::TreeViewMulti() : Gtk::TreeView(), pending(false) { get_selection()->set_select_function( [this](Glib::RefPtr const&, Gtk::TreeModel::Path const&, bool) { return !pending; }); } enum class Type { COMMAND, KEY, TEXT, SCROLL, IGNORE, BUTTON, /* MISC, */ GLOBAL, VIEW, PLUGIN, TOUCHPAD }; struct TypeInfo { Type type; const char *name; const std::type_info *type_info; const CellRendererTextishMode mode; }; static constexpr TypeInfo all_types[] = { { Type::COMMAND, N_("Command"), &typeid(Command), CELL_RENDERER_TEXTISH_MODE_Popup }, { Type::KEY, N_("Key"), &typeid(SendKey), CELL_RENDERER_TEXTISH_MODE_Key }, // { Type::TEXT, N_("Text"), &typeid(SendText), CELL_RENDERER_TEXTISH_MODE_Text }, // { Type::SCROLL, N_("Scroll"), &typeid(Scroll), CELL_RENDERER_TEXTISH_MODE_Key }, { Type::IGNORE, N_("Ignore"), &typeid(Ignore), CELL_RENDERER_TEXTISH_MODE_Key }, { Type::BUTTON, N_("Button"), &typeid(Button), CELL_RENDERER_TEXTISH_MODE_Popup }, // { Type::MISC, N_("WM Action (old)"), &typeid(Misc), CELL_RENDERER_TEXTISH_MODE_Combo }, { Type::GLOBAL, N_("Global Action"), &typeid(Global), CELL_RENDERER_TEXTISH_MODE_Combo }, { Type::VIEW, N_("WM Action"), &typeid(View), CELL_RENDERER_TEXTISH_MODE_Combo }, { Type::PLUGIN, N_("Custom Plugin"), &typeid(Plugin), CELL_RENDERER_TEXTISH_MODE_Text }, { Type::TOUCHPAD,N_("Touchpad Gesture"),&typeid(Touchpad), CELL_RENDERER_TEXTISH_MODE_Popup }, { Type::COMMAND, 0, 0, CELL_RENDERER_TEXTISH_MODE_Text } }; static Type from_name(const Glib::ustring& name) { for (const TypeInfo* i = all_types; i->name; i++) if (!i->name || _(i->name) == name) return i->type; return Type::COMMAND; /* not reached */ } static const TypeInfo& type_info_from_name(const Glib::ustring& name) { for (const TypeInfo* i = all_types; i->name; i++) if (!i->name || _(i->name) == name) return *i; return all_types[7]; /* not reached */ } static constexpr const char *type_info_to_name(const std::type_info *info) { for (const TypeInfo* i = all_types; i->name; i++) if (i->type_info == info) return _(i->name); return ""; } static Glib::ustring get_action_label(const Action* action); static void on_actions_cell_data_arg(G_GNUC_UNUSED GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter); gchar *path_string = gtk_tree_path_to_string(path); ((Actions *)data)->on_cell_data_arg(cell, path_string); g_free(path_string); gtk_tree_path_free(path); } static void on_actions_accel_edited(CellRendererTextish *, gchar *path, GdkModifierType mods, guint code, gpointer data) { ((Actions *)data)->on_accel_edited(path, code, mods); } static void on_actions_combo_edited(CellRendererTextish *, gchar *path, guint row, gpointer data) { ((Actions *)data)->on_combo_edited(path, row); } static void on_actions_text_edited(GtkCellRendererText *, gchar *path, gchar *new_text, gpointer data) { ((Actions *)data)->on_text_edited(path, new_text); } static void on_actions_editing_started(GtkCellRenderer *, GtkCellEditable *editable, const gchar *path, gpointer data) { ((Actions *)data)->on_arg_editing_started(editable, path); } static void on_stroke_editing_started(GtkCellRenderer *, GtkCellEditable *, const gchar *path, gpointer data) { ((Actions *)data)->on_stroke_editing(path); } void Actions::startup(Gtk::Application* app, Gtk::Dialog* message_dialog) { { Gtk::Window* tmp = nullptr; widgets->get_widget("main", tmp); main_win.reset(tmp); if(message_dialog) { message_dialog->set_transient_for(*main_win); message_dialog->set_modal(true); main_win->signal_show().connect([message_dialog]() { message_dialog->show(); message_dialog->run(); delete message_dialog; }); } app->add_window(*tmp); } chooser.startup(); timeout = Glib::TimeoutSource::create(5000); action_list = actions.get_root(); Gtk::ScrolledWindow *sw; widgets->get_widget("scrolledwindow_actions", sw); widgets->get_widget("treeview_apps", apps_view); sw->add(tv); tv.show(); Gtk::AboutDialog *about_dialog; widgets->get_widget("about-dialog", about_dialog); about_dialog->set_wrap_license(true); about_dialog->signal_response().connect([about_dialog](G_GNUC_UNUSED int response_id) { about_dialog->hide(); }); Gtk::Button *button_add, *button_add_app, *button_add_group, *button_about; widgets->get_widget("button_add_action", button_add); widgets->get_widget("button_delete_action", button_delete); widgets->get_widget("button_record", button_record); widgets->get_widget("button_add_app", button_add_app); widgets->get_widget("button_add_group", button_add_group); widgets->get_widget("button_remove_app", button_remove_app); widgets->get_widget("button_reset_actions", button_reset_actions); widgets->get_widget("button_about", button_about); widgets->get_widget("check_show_deleted", check_show_deleted); Gtk::Button *button_import, *button_export; Gtk::LinkButton *import_easystroke, *import_default; widgets->get_widget("import_dialog", import_dialog); widgets->get_widget("button_import", button_import); widgets->get_widget("button_export", button_export); widgets->get_widget("import_import", button_import_import); widgets->get_widget("import_cancel", button_import_cancel); widgets->get_widget("import_easystroke", import_easystroke); widgets->get_widget("import_default", import_default); widgets->get_widget("import_file_chooser", import_file_chooser); widgets->get_widget("import_add", import_add); widgets->get_widget("import_info", import_info); widgets->get_widget("import_info_label", import_info_label); button_export->signal_clicked().connect([this]() { try_export(); }); button_import->signal_clicked().connect([this]() { import_dialog->show_all(); }); button_import_cancel->signal_clicked().connect([this]() { import_dialog->close(); }); button_import_import->signal_clicked().connect([this]() { try_import(); }); import_easystroke->signal_activate_link().connect([this]() { std::string old_config_dir = std::string(getenv("HOME")) + "/.easystroke/"; std::error_code ec; bool found = false; if(std::filesystem::exists(old_config_dir, ec) && std::filesystem::is_directory(config_dir, ec)) { for(const char* const * x = ActionDB::easystroke_actions_versions; *x; ++x) { std::string fn = old_config_dir + *x; if(std::filesystem::exists(fn, ec) && std::filesystem::is_regular_file(fn, ec)) { import_file_chooser->set_filename(fn); found = true; break; } } } if(!found) { import_info_label->set_text(_("Cannot find Easystroke configuration. Make sure that Easystroke is properly installed.")); import_info->show_all(); gtk_info_bar_set_revealed(import_info->gobj(), TRUE); } else gtk_info_bar_set_revealed(import_info->gobj(), FALSE); return true; }, false); import_default->signal_activate_link().connect([this]() { std::string fn = std::string(DATA_DIR) + "/" + ActionDB::wstroke_actions_versions[0]; std::error_code ec; if(std::filesystem::exists(fn, ec) && std::filesystem::is_regular_file(fn, ec)) { import_file_chooser->set_filename(fn); gtk_info_bar_set_revealed(import_info->gobj(), FALSE); } else { import_info_label->set_text(_("Cannot find the default configuration. Make sure that WStroke is properly installed.")); import_info->show_all(); gtk_info_bar_set_revealed(import_info->gobj(), TRUE); } return true; }, false); import_info->signal_response().connect([this] (auto) { gtk_info_bar_set_revealed(import_info->gobj(), FALSE); }); import_file_chooser->signal_file_set().connect([this] () { gtk_info_bar_set_revealed(import_info->gobj(), FALSE); }); button_record->signal_clicked().connect([this]() { Gtk::TreeModel::Path path; Gtk::TreeViewColumn *col; tv.get_cursor(path, col); Gtk::TreeRow row(*tm->get_iter(path)); on_row_activated(row); }); button_delete->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_button_delete)); button_add->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_button_new)); button_add_app->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_add_app)); button_add_group->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_add_group)); button_remove_app->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_remove_app)); button_reset_actions->signal_clicked().connect([this]() { std::vector paths = tv.get_selection()->get_selected_rows(); for(const auto& x : paths) { Gtk::TreeRow row(*tm->get_iter(x)); action_list->reset(row[cols.id]); } update_action_list(); on_selection_changed(); update_actions(); }); button_about->signal_clicked().connect([about_dialog](){ about_dialog->run(); }); tv.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &Actions::on_selection_changed)); tm = Store::create(cols, this); tv.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE); int n; CellRendererTextish *stroke_renderer = cell_renderer_textish_new(); stroke_renderer->mode = CELL_RENDERER_TEXTISH_MODE_Popup; GtkTreeViewColumn *col_stroke = gtk_tree_view_column_new(); gtk_tree_view_column_pack_start(col_stroke, GTK_CELL_RENDERER (stroke_renderer), TRUE); gtk_tree_view_column_add_attribute(col_stroke, GTK_CELL_RENDERER (stroke_renderer), "icon", cols.stroke.index()); gtk_tree_view_column_set_title(col_stroke, _("Stroke")); gtk_tree_view_column_set_sort_column_id(col_stroke, cols.id.index()); gtk_tree_view_append_column(tv.gobj(), col_stroke); g_object_set(stroke_renderer, "editable", true, nullptr); g_signal_connect(stroke_renderer, "editing-started", G_CALLBACK(on_stroke_editing_started), this); tm->set_sort_func(cols.id, sigc::mem_fun(*this, &Actions::compare_ids)); tm->set_default_sort_func(sigc::mem_fun(*this, &Actions::compare_ids)); tm->set_sort_column(Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID, Gtk::SORT_ASCENDING); n = tv.append_column(_("Name"), cols.name); Gtk::CellRendererText *name_renderer = dynamic_cast(tv.get_column_cell_renderer(n-1)); name_renderer->property_editable() = true; name_renderer->signal_edited().connect(sigc::mem_fun(*this, &Actions::on_name_edited)); name_renderer->signal_editing_started().connect([this] (G_GNUC_UNUSED Gtk::CellEditable* editable, G_GNUC_UNUSED const Glib::ustring& path) { editing = true; }); name_renderer->signal_editing_canceled().connect([this] () { editing_new = false; }); Gtk::TreeView::Column *col_name = tv.get_column(n-1); col_name->set_sort_column(cols.name); col_name->set_cell_data_func(*name_renderer, sigc::mem_fun(*this, &Actions::on_cell_data_name)); col_name->set_resizable(); type_store = Gtk::ListStore::create(type); for(const TypeInfo *i = all_types; i->name; i++) (*(type_store->append()))[type.type] = _(i->name); Gtk::CellRendererCombo *type_renderer = Gtk::manage(new Gtk::CellRendererCombo); type_renderer->property_model() = type_store; type_renderer->property_editable() = true; type_renderer->property_text_column() = 0; type_renderer->property_has_entry() = false; type_renderer->signal_edited().connect(sigc::mem_fun(*this, &Actions::on_type_edited)); type_renderer->signal_editing_started().connect([this] (G_GNUC_UNUSED Gtk::CellEditable* editable, G_GNUC_UNUSED const Glib::ustring& path) { editing = true; }); type_renderer->signal_editing_canceled().connect([this] () { editing_new = false; }); n = tv.append_column(_("Type"), *type_renderer); Gtk::TreeView::Column *col_type = tv.get_column(n-1); col_type->add_attribute(type_renderer->property_text(), cols.type); col_type->set_cell_data_func(*type_renderer, sigc::mem_fun(*this, &Actions::on_cell_data_type)); CellRendererTextish *arg_renderer = cell_renderer_textish_new(); GtkCellRenderer *cmd_renderer = gtk_cell_renderer_text_new(); GtkTreeViewColumn *col_arg = gtk_tree_view_column_new(); gtk_tree_view_column_pack_start(col_arg, GTK_CELL_RENDERER (arg_renderer), TRUE); gtk_tree_view_column_pack_start(col_arg, cmd_renderer, FALSE); gtk_tree_view_column_add_attribute(col_arg, GTK_CELL_RENDERER (arg_renderer), "text", cols.arg.index()); gtk_tree_view_column_add_attribute(col_arg, GTK_CELL_RENDERER (arg_renderer), "icon", cols.action_icon.index()); gtk_tree_view_column_add_attribute(col_arg, cmd_renderer, "text", cols.cmd_path.index()); gtk_tree_view_column_set_title(col_arg, _("Details")); gtk_tree_view_append_column(tv.gobj(), col_arg); gtk_tree_view_column_set_cell_data_func (col_arg, GTK_CELL_RENDERER (arg_renderer), on_actions_cell_data_arg, this, nullptr); gtk_tree_view_column_set_resizable(col_arg, true); g_object_set(arg_renderer, "editable", true, nullptr); g_object_set(cmd_renderer, "editable", true, nullptr); g_object_set(cmd_renderer, "max-width-chars", 35, nullptr); g_object_set(cmd_renderer, "ellipsize", PANGO_ELLIPSIZE_END, nullptr); g_signal_connect(arg_renderer, "key-edited", G_CALLBACK(on_actions_accel_edited), this); g_signal_connect(arg_renderer, "combo-edited", G_CALLBACK(on_actions_combo_edited), this); g_signal_connect(arg_renderer, "edited", G_CALLBACK(on_actions_text_edited), this); g_signal_connect(cmd_renderer, "edited", G_CALLBACK(on_actions_text_edited), this); g_signal_connect(arg_renderer, "editing-started", G_CALLBACK(on_actions_editing_started), this); load_command_infos(); update_action_list(); tv.set_model(tm); tv.enable_model_drag_source(); tv.enable_model_drag_dest(); check_show_deleted->signal_toggled().connect(sigc::mem_fun(*this, &Actions::update_action_list)); apps_view->get_selection()->signal_changed().connect(sigc::mem_fun(*this, &Actions::on_apps_selection_changed)); apps_model = AppsStore::create(ca, this); load_app_list(apps_model->children(), actions.get_root()); update_counts(); apps_view->append_column_editable(_("Application"), ca.app); apps_view->get_column(0)->set_expand(true); apps_view->get_column(0)->set_cell_data_func(*apps_view->get_column_cell_renderer(0), [this](Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) { ActionListDiff *as = (*iter)[ca.actions]; Gtk::CellRendererText *renderer = dynamic_cast(cell); if (renderer) renderer->property_editable().set_value(actions.get_root() != as && !as->app); }); Gtk::CellRendererText *app_name_renderer = dynamic_cast(apps_view->get_column_cell_renderer(0)); app_name_renderer->signal_edited().connect([this](const Glib::ustring& path, const Glib::ustring& new_text) { Gtk::TreeRow row(*apps_model->get_iter(path)); row[ca.app] = new_text; ActionListDiff *as = row[ca.actions]; as->name = new_text; update_actions(); }); apps_view->append_column(_("Actions"), ca.count); apps_view->set_model(apps_model); apps_view->enable_model_drag_dest(); apps_view->expand_all(); /* list of exceptions */ Gtk::Button *add_exception, *remove_exception; widgets->get_widget("button_add_exception", add_exception); widgets->get_widget("button_remove_exception", remove_exception); widgets->get_widget("treeview_exceptions", exclude_tv); exclude_tm = Gtk::ListStore::create(exclude_cols); exclude_tv->set_model(exclude_tm); exclude_tv->append_column(_("Application (WM__CLASS)"), exclude_cols.type); exclude_tm->set_sort_column(exclude_cols.type, Gtk::SORT_ASCENDING); add_exception->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_add_exclude)); remove_exception->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_remove_exclude)); for(const std::string& cl : actions.get_exclude_apps()) { Gtk::TreeModel::Row row = *(exclude_tm->append()); row[exclude_cols.type] = cl; } /* timeout for saving actions */ timeout->connect([this] () { if(exiting) return false; if(actions_changed) { save_actions(); actions_changed = false; } return true; }); timeout->attach(); /* dialog to select touchpad actions */ Gtk::RadioButton *radio_scroll; Gtk::SpinButton *spin_fingers; widgets->get_widget("touchpad_type_scroll", radio_scroll); widgets->get_widget("touchpad_fingers", spin_fingers); radio_scroll->signal_toggled().connect([radio_scroll, spin_fingers](){ spin_fingers->set_sensitive(!radio_scroll->get_active()); }); main_win->show(); } static Glib::ustring app_name_hr(Glib::ustring src) { return src == "" ? _("") : src; } static bool get_app_id_dialog_fallback(std::string& app_id) { Gtk::Dialog dialog("Add new app", true); auto x = dialog.get_content_area(); Gtk::Label label("Please enter the app ID of the application to add:"); Gtk::Entry app_id_entry; x->pack_start(label, false, false, 10); x->pack_start(app_id_entry, false, false, 10); label.show(); app_id_entry.show(); dialog.add_button("OK", Gtk::RESPONSE_OK); dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); int r = dialog.run(); if(r == Gtk::RESPONSE_OK) { app_id = app_id_entry.get_text(); return true; } return false; } struct app_id_dialog_data { std::string& app_id; Gtk::Dialog* dialog; app_id_dialog_data(std::string& app_id_, Gtk::Dialog* dialog_): app_id(app_id_), dialog(dialog_) { } }; static void app_id_cb(void* p, tl_grabber* gr) { auto data = (app_id_dialog_data*)p; char* app_id = toplevel_grabber_get_app_id(gr); toplevel_grabber_set_callback(gr, nullptr, nullptr); if(!app_id) { fprintf(stderr, "Cannot get app ID of selected toplevel view!\n"); data->dialog->response(Gtk::RESPONSE_NONE); } data->app_id = std::string(app_id); free(app_id); data->dialog->response(Gtk::RESPONSE_OK); } static bool get_app_id_dialog(std::string& app_id, Gtk::Window& main_win) { auto gdk_display = Gdk::Display::get_default(); struct tl_grabber* gr = nullptr; struct wl_display* dpy = nullptr; #ifdef GDK_WINDOWING_WAYLAND { auto tmp = gdk_display->gobj(); if(GDK_IS_WAYLAND_DISPLAY(tmp)) { dpy = gdk_wayland_display_get_wl_display(gdk_display->gobj()); gr = toplevel_grabber_new(dpy, nullptr, nullptr); } } #endif if(!gr) { fprintf(stderr, "Cannot initiate foreign toplevel grabber interface, falling back to manual entry of app ID\n"); return get_app_id_dialog_fallback(app_id); } Gtk::Dialog dialog("Add new app", true); dialog.set_transient_for(main_win); auto x = dialog.get_content_area(); Gtk::Label label("Please select the app to add by clicking on it or click Cancel to enter the app ID manually"); x->pack_start(label, false, false, 10); label.show(); dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); app_id_dialog_data data(app_id, &dialog); toplevel_grabber_set_callback(gr, app_id_cb, &data); int r = dialog.run(); dialog.hide(); wl_display_roundtrip(dpy); auto seat = gdk_display->get_default_seat(); struct wl_seat* wl_seat = gdk_wayland_seat_get_wl_seat(seat->gobj()); toplevel_grabber_activate_app(gr, "wstroke-config", wl_seat, 1); toplevel_grabber_free(gr); switch(r) { case Gtk::RESPONSE_OK: return true; case Gtk::RESPONSE_CANCEL: return get_app_id_dialog_fallback(app_id); default: return false; } } template static void set_tree_to_item(ModelRef& model, Gtk::TreeView& tv, const Gtk::TreeModelColumn& col, const KeyType& x) { model->foreach([&x, &col, &tv] (const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter) { if ((*iter)[col] == x) { tv.set_cursor(path); return true; } return false; }); } void Actions::on_add_exclude() { std::string name; if (!get_app_id_dialog(name, *main_win)) return; if (actions.add_exclude_app(name)) { Gtk::TreeModel::Row row = *(exclude_tm->append()); row[exclude_cols.type] = name; Gtk::TreePath path = exclude_tm->get_path(row); exclude_tv->set_cursor(path); } else set_tree_to_item(exclude_tm, *exclude_tv, exclude_cols.type, name); } void Actions::on_remove_exclude() { Gtk::TreePath path; Gtk::TreeViewColumn *col; exclude_tv->get_cursor(path, col); if (path.gobj() != 0) { Gtk::TreeIter iter = *exclude_tm->get_iter(path); Glib::ustring tmp = (*iter)[exclude_cols.type]; if (!actions.remove_exclude_app(tmp)) { fprintf(stderr, "Erased app from exclude list (%s) not found!\n", tmp.c_str()); } exclude_tm->erase(iter); } } void Actions::load_app_list(const Gtk::TreeNodeChildren &ch, ActionListDiff *actions) { Gtk::TreeRow row = *(apps_model->append(ch)); row[ca.app] = app_name_hr(actions->name); row[ca.actions] = actions; for (auto i = actions->begin(); i != actions->end(); i++) load_app_list(row.children(), &(*i)); } void Actions::on_cell_data_name(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) { bool bold = (*iter)[cols.name_bold]; bool deactivated = (*iter)[cols.deactivated]; Gtk::CellRendererText *renderer = dynamic_cast(cell); if (renderer) renderer->property_weight().set_value(bold ? 700 : 400); cell->property_sensitive().set_value(!deactivated); } void Actions::on_cell_data_type(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) { bool bold = (*iter)[cols.action_bold]; bool deactivated = (*iter)[cols.deactivated]; Gtk::CellRendererText *renderer = dynamic_cast(cell); if (renderer) renderer->property_weight().set_value(bold ? 700 : 400); cell->property_sensitive().set_value(!deactivated); } void Actions::on_cell_data_arg(GtkCellRenderer *cell, gchar *path) { Gtk::TreeModel::iterator iter = tm->get_iter(path); bool bold = (*iter)[cols.action_bold]; bool deactivated = (*iter)[cols.deactivated]; g_object_set(cell, "sensitive", !deactivated, "weight", bold ? 700 : 400, nullptr); CellRendererTextish *renderer = CELL_RENDERER_TEXTISH (cell); if (!renderer) return; Glib::ustring str = (*iter)[cols.type]; const auto& type_info = type_info_from_name(str); renderer->mode = type_info.mode; if(type_info.type == Type::GLOBAL) cell_renderer_textish_set_items(renderer, const_cast(Global::types), Global::n_actions); else if(type_info.type == Type::VIEW) cell_renderer_textish_set_items(renderer, const_cast(View::types), View::n_actions); } int Actions::compare_ids(const Gtk::TreeModel::iterator &a, const Gtk::TreeModel::iterator &b) { unsigned int x = actions.get_stroke_order((*a)[cols.id]); unsigned int y = actions.get_stroke_order((*b)[cols.id]); if(x < y) return -1; if(x > y) return 1; return 0; } bool Actions::AppsStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData &selection) const { static bool expecting = false; static Gtk::TreePath expected; if (expecting && expected != dest) expecting = false; if (!expecting) { if (gtk_tree_path_get_depth((GtkTreePath *)dest.gobj()) < 2 || dest.back() != 0) return false; expected = dest; expected.up(); expecting = true; return false; } expecting = false; Gtk::TreePath src; Glib::RefPtr model; if (!Gtk::TreeModel::Path::get_from_selection_data(selection, model, src)) return false; if (model != parent->tm) return false; Gtk::TreeIter dest_iter = parent->apps_model->get_iter(dest); ActionListDiff *actions = dest_iter ? (*dest_iter)[parent->ca.actions] : (ActionListDiff *)nullptr; return actions && actions != parent->action_list; } bool Actions::AppsStore::drag_data_received_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData &selection) { Gtk::TreePath src; Glib::RefPtr model; if (!Gtk::TreeModel::Path::get_from_selection_data(selection, model, src)) return false; if (model != parent->tm) return false; Gtk::TreeIter dest_iter = parent->apps_model->get_iter(dest); ActionListDiff *actions = dest_iter ? (*dest_iter)[parent->ca.actions] : (ActionListDiff *)nullptr; if (!actions || actions == parent->action_list) return false; Glib::RefPtr sel = parent->tv.get_selection(); if (sel->count_selected_rows() > 1) { /* we temporarily unset soring so as not to resort the list every * time an item is removed */ int col; Gtk::SortType sort; parent->tm->get_sort_column_id(col, sort); parent->tm->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, sort); std::vector paths = sel->get_selected_rows(); std::vector refs; for(const auto& x : paths) refs.push_back(Gtk::TreeRowReference(parent->tm, x)); for(const auto& x : refs) { Gtk::TreeIter i = parent->tm->get_iter(x.get_path()); stroke_id id = (*i)[parent->cols.id]; if(parent->actions.move_stroke_to_app(parent->action_list, actions, id)) parent->tm->erase(i); else parent->update_row(*i); } parent->tm->set_sort_column(col, sort); } else { auto i = parent->tm->get_iter(src); stroke_id src_id = (*i)[parent->cols.id]; if(parent->actions.move_stroke_to_app(parent->action_list, actions, src_id)) parent->tm->erase(i); else parent->update_row(*i); } // parent->update_action_list(); parent->update_actions(); return true; } bool Actions::Store::row_draggable_vfunc(const Gtk::TreeModel::Path&) const { int col; Gtk::SortType sort; parent->tm->get_sort_column_id(col, sort); if(col == Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID || col == parent->cols.id.index()) return true; else return false; } bool Actions::Store::row_drop_possible_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData&) const { static bool ignore_next = false; if (gtk_tree_path_get_depth((GtkTreePath *)dest.gobj()) > 1) { // this gets triggered when the drag is over an existing row (as opposed to being "in between" rows) ignore_next = true; return false; } if (ignore_next) { ignore_next = false; return false; } return true; } bool Actions::Store::drag_data_received_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData &selection) { Gtk::TreePath src; Glib::RefPtr model; if (!Gtk::TreeModel::Path::get_from_selection_data(selection, model, src)) return false; if (model != parent->tm) return false; int col; Gtk::SortType sort; parent->tm->get_sort_column_id(col, sort); if( !(col == Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID || col == parent->cols.id.index()) ) return false; stroke_id src_id = (*parent->tm->get_iter(src))[parent->cols.id]; Gtk::TreeIter dest_iter = parent->tm->get_iter(dest); stroke_id dest_id = dest_iter ? (*dest_iter)[parent->cols.id] : 0; Glib::RefPtr sel = parent->tv.get_selection(); if (sel->count_selected_rows() <= 1) { parent->actions.move_stroke(src_id, dest_id, sort == Gtk::SORT_DESCENDING); (*parent->tm->get_iter(src))[parent->cols.id] = src_id; parent->update_actions(); } else { /* note: we temporarily unset sorting so that the list is not resorted for each update */ parent->tm->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, sort); std::vector paths = sel->get_selected_rows(); std::vector ids; for(const auto& p : paths) ids.push_back((*parent->tm->get_iter(p))[parent->cols.id]); if(sort == Gtk::SORT_DESCENDING) parent->actions.move_strokes(ids.rbegin(), ids.rend(), dest_id, true); else parent->actions.move_strokes(ids.begin(), ids.end(), dest_id, false); parent->tm->set_sort_column(col, sort); // this will apply the new sort parent->update_actions(); } return false; } void Actions::on_type_edited(const Glib::ustring &path, const Glib::ustring &new_text) { tv.grab_focus(); Gtk::TreeRow row(*tm->get_iter(path)); Type new_type = from_name(new_text); Type old_type = from_name(row[cols.type]); bool edit = true; if (old_type == new_type) { edit = editing_new; } else { row[cols.type] = new_text; std::unique_ptr new_action; if (old_type == Type::COMMAND) { row[cols.cmd_save] = (Glib::ustring)row[cols.arg]; } if (old_type == Type::PLUGIN) { row[cols.plugin_action_save] = (Glib::ustring)row[cols.arg]; } switch(new_type) { case Type::COMMAND: { Glib::ustring cmd_save = row[cols.cmd_save]; if (cmd_save != "") edit = false; new_action = Command::create(cmd_save); } break; case Type::KEY: new_action = SendKey::create(0, (Gdk::ModifierType)0); edit = true; break; case Type::TEXT: new_action = SendText::create(Glib::ustring()); edit = true; break; case Type::SCROLL: new_action = Scroll::create((Gdk::ModifierType)0); edit = false; break; case Type::IGNORE: new_action = Ignore::create((Gdk::ModifierType)0); edit = false; break; case Type::BUTTON: new_action = Button::create((Gdk::ModifierType)0, 0); edit = true; break; case Type::GLOBAL: new_action = Global::create(Global::Type::NONE); edit = true; break; case Type::VIEW: new_action = View::create(View::Type::NONE); edit = true; break; case Type::PLUGIN: { Glib::ustring cmd_save = row[cols.plugin_action_save]; if (cmd_save != "") edit = false; new_action = Plugin::create(cmd_save); } break; case Type::TOUCHPAD: new_action = Touchpad::create(Touchpad::Type::NONE, 2, 0); edit = true; break; } action_list->set_action(row[cols.id], std::move(new_action)); update_row(row); update_actions(); } editing_new = false; if (! (new_type == Type::VIEW || new_type == Type::GLOBAL)) focus(row[cols.id], 3, edit); } void Actions::on_button_delete() { bool show_deleted = check_show_deleted->get_active(); unsigned int to_delete = 0, to_disable = 0; Glib::RefPtr sel = tv.get_selection(); std::vector paths = sel->get_selected_rows(); for(const auto& x : paths) { stroke_id id = (*tm->get_iter(x))[cols.id]; if(actions.get_stroke_owner(id) == action_list) to_delete++; else to_disable++; } if(to_delete) { Glib::ustring str; if (to_delete == 1) for(const auto& x : paths) { auto tmp = Gtk::TreeRow(*tm->get_iter(x)); if(actions.get_stroke_owner(tmp[cols.id]) == action_list) { str = Glib::ustring::compose(_("Action \"%1\" is about to be deleted."), tmp[cols.name]); break; } } else str = Glib::ustring::compose(ngettext("One action is about to be deleted", "%1 actions are about to be deleted", to_delete), to_delete); if(to_disable) str += Glib::ustring::compose(ngettext(" (one additional action will be disabled).", " (%1 additional actions will be disabled).,", to_disable), to_disable); else str += "."; Gtk::MessageDialog *dialog; widgets->get_widget("dialog_delete", dialog); dialog->set_message(ngettext("Delete an Action", "Delete Actions", to_delete)); dialog->set_secondary_text(str); Gtk::Button *del; widgets->get_widget("button_delete_delete", del); dialog->show(); del->grab_focus(); bool ok = dialog->run() == 1; dialog->hide(); if (!ok) return; } if (to_delete + to_disable == 1) { auto i = tm->get_iter(*paths.begin()); stroke_id id = (*i)[cols.id]; if(!(to_disable && show_deleted)) tm->erase(i); actions.remove_stroke(action_list, id); if(to_disable && show_deleted) update_row(*i); } else { /* note: we temporarily unset sorting so that the list is not resorted for each update */ int col; Gtk::SortType sort; tm->get_sort_column_id(col, sort); tm->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, sort); std::vector refs; std::vector ids; for(const auto& x : paths) { refs.push_back(Gtk::TreeRowReference(tm, x)); ids.push_back((*tm->get_iter(x))[cols.id]); } auto end = std::remove_if(refs.begin(), refs.end(), [this, show_deleted](const auto& x) { Gtk::TreeIter i = tm->get_iter(x.get_path()); bool remove = !show_deleted || (actions.get_stroke_owner((*i)[cols.id]) == action_list); if(remove) tm->erase(i); return show_deleted && remove; }); actions.remove_strokes(action_list, ids.begin(), ids.end()); if(show_deleted) for(auto it = refs.begin(); it != end; ++it) update_row(*tm->get_iter(it->get_path())); /* apply sorting to the new selection */ tm->set_sort_column(col, sort); } if(show_deleted && to_disable) button_reset_actions->set_sensitive(true); update_actions(); update_counts(); } bool Actions::get_action_item(const ActionListDiff* x, Gtk::TreeIter& it) { bool found = false; apps_model->foreach_iter([this, x, &it, &found] (const Gtk::TreeModel::iterator& iter) { if ((*iter)[ca.actions] == x) { it = iter; found = true; return true; } return false; }); return found; } void Actions::on_add_app() { std::string name; if (!get_app_id_dialog(name, *main_win)) return; const ActionListDiff* name_match = actions.get_action_list(name); if(name_match) { set_tree_to_item(apps_model, *apps_view, ca.actions, name_match); return; } ActionListDiff *parent = action_list->app ? action_list->get_parent() : action_list; if (!parent || parent->app) throw std::runtime_error("Expected app group as parent node of an app!\n"); Gtk::TreeModel::iterator tree_it; if(!get_action_item(parent, tree_it)) throw std::runtime_error("Expected group not found in the app list!\n"); ActionListDiff *child = actions.add_app(parent, name, true); Gtk::TreeRow row = *(apps_model->append(tree_it->children())); row[ca.app] = app_name_hr(name); row[ca.actions] = child; row[ca.count] = child->count_actions(); Gtk::TreePath path = apps_model->get_path(row); apps_view->expand_to_path(path); apps_view->set_cursor(path); update_actions(); } void Actions::on_remove_app() { if (action_list == actions.get_root()) return; int size = action_list->size_rec(); if (size) { Gtk::MessageDialog *dialog; widgets->get_widget("dialog_delete", dialog); Glib::ustring str = Glib::ustring::compose(_("%1 \"%2\" (containing %3 %4) is about to be deleted."), action_list->app ? _("The application") : _("The group"), action_list->name, size, ngettext("action", "actions", size)); dialog->set_message(action_list->app ? _("Delete an Application") : _("Delete an Application Group")); dialog->set_secondary_text(str); Gtk::Button *del; widgets->get_widget("button_delete_delete", del); dialog->show(); del->grab_focus(); bool ok = dialog->run() == 1; dialog->hide(); if (!ok) return; } actions.remove_app(action_list); apps_model->erase(*apps_view->get_selection()->get_selected()); update_actions(); } void Actions::on_add_group() { ActionListDiff *parent = action_list; Glib::ustring name = _("Group"); Gtk::TreeModel::iterator tree_it; if(!get_action_item(parent, tree_it)) throw std::runtime_error("Expected item not found in the app list!\n"); Gtk::TreeRow row = *(apps_model->append(tree_it->children())); ActionListDiff *child; Gtk::TreePath path; if(parent->app) { /* we convert this app to a new group and add the app as its child */ child = actions.add_app(parent, parent->name, true); parent->app = false; parent->name = name; row[ca.app] = app_name_hr(child->name); (*tree_it)[ca.app] = name; path = apps_model->get_path(*tree_it); } else { /* parent is a group, we add a group as its child */ child = parent->add_child(name, false); row[ca.app] = name; path = apps_model->get_path(row); } row[ca.actions] = child; row[ca.count] = child->count_actions(); apps_view->expand_to_path(path); apps_view->set_cursor(path, *apps_view->get_column(0), true); update_actions(); } void Actions::on_apps_selection_changed() { ActionListDiff *new_action_list = actions.get_root(); if (apps_view->get_selection()->count_selected_rows()) { Gtk::TreeIter i = apps_view->get_selection()->get_selected(); new_action_list = (*i)[ca.actions]; } button_remove_app->set_sensitive(new_action_list != actions.get_root()); if (action_list != new_action_list) { action_list = new_action_list; update_action_list(); on_selection_changed(); } } void Actions::update_counts() { apps_model->foreach_iter([this](const Gtk::TreeIter &i) { (*i)[ca.count] = ((ActionListDiff*)(*i)[ca.actions])->count_actions(); return false; }); } void Actions::update_action_list() { check_show_deleted->set_sensitive(action_list != actions.get_root()); std::set ids = action_list->get_ids(check_show_deleted->get_active()); /* note: we temporarily unset sorting so that the list is not resorted for each update */ int col; Gtk::SortType sort; tm->get_sort_column_id(col, sort); tm->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, sort); const Gtk::TreeNodeChildren &ch = tm->children(); std::list refs; for(const auto& x : ch) refs.push_back(Gtk::TreeRowReference(tm, Gtk::TreePath(x))); for(const auto& ref : refs) { Gtk::TreeIter i = tm->get_iter(ref.get_path()); Gtk::TreeRow row = *i; auto it = ids.find(row[cols.id]); if (it == ids.end()) { tm->erase(i); } else { ids.erase(it); update_row(row); } } for(const auto& x : ids) { Gtk::TreeRow row = *tm->append(); row[cols.id] = x; update_row(row); } /* apply sorting to the new selection */ tm->set_sort_column(col, sort); } void Actions::update_row(const Gtk::TreeRow& row) { StrokeRow si = action_list->get_info(row[cols.id]); row[cols.stroke] = (si.stroke && !si.stroke->trivial()) ? StrokeDrawer::draw((si.stroke), STROKE_SIZE, si.stroke_overwrite ? 4.0 : 2.0) : StrokeDrawer::drawEmpty(STROKE_SIZE); row[cols.name] = *si.name; row[cols.type] = si.action ? type_info_to_name(&typeid(*si.action)) : ""; row[cols.arg] = get_action_label(si.action); row[cols.deactivated] = si.deleted; row[cols.name_bold] = si.name_overwrite; row[cols.action_bold] = si.action_overwrite; row[cols.action_icon] = Glib::RefPtr(); row[cols.cmd_path] = ""; button_reset_actions->set_sensitive(si.stroke_overwrite || si.deleted || si.name_overwrite || si.action_overwrite); if(si.action) { const Command* tmp = dynamic_cast(si.action); if(tmp) { Glib::ustring cmd1 = row[cols.arg]; row[cols.cmd_path] = cmd1; auto it = command_info.find(tmp->desktop_file); if(it != command_info.end()) { row[cols.arg] = it->second.name; row[cols.action_icon] = it->second.icon; row[cols.custom_command] = false; } else { row[cols.arg] = _("Custom command: "); auto icon_theme = Gtk::IconTheme::get_default(); auto pb = icon_theme->load_icon("application-x-executable", 32); if(pb) { if(pb->get_width() > 32) pb = pb->scale_simple(32, 32, Gdk::INTERP_BILINEAR); row[cols.action_icon] = pb; } row[cols.custom_command] = true; } } } } class Actions::OnStroke { Actions *parent; Gtk::Dialog *dialog; Gtk::TreeRow &row; public: void run(Stroke* stroke) { parent->action_list->set_stroke(row[parent->cols.id], std::move(*stroke)); parent->update_row(row); parent->on_selection_changed(); parent->update_actions(); dialog->response(0); } OnStroke(Actions *parent_, Gtk::Dialog *dialog_, Gtk::TreeRow &row_) : parent(parent_), dialog(dialog_), row(row_) {} }; void Actions::on_stroke_editing(const char* path) { Gtk::TreeRow row(*tm->get_iter(path)); on_row_activated(row); } void Actions::on_row_activated(Gtk::TreeRow& row) { Gtk::MessageDialog *dialog; static SRArea *drawarea = nullptr; widgets->get_widget("dialog_record", dialog); if(input_inhibitor_grab()) dialog->set_secondary_text(Glib::ustring::compose(_("The next stroke will be associated with the action \"%1\". You can draw it in the area below, using any pointer button."), row[cols.name])); else dialog->set_secondary_text(Glib::ustring::compose(_("The next stroke will be associated with the action \"%1\". You can draw it in the area below. You may need to use a different pointer button than the one normally used for gestures."), row[cols.name])); if(!drawarea) { drawarea = Gtk::manage(new SRArea()); drawarea->set_size_request(600, 400); auto box = dialog->get_content_area(); box->pack_start(*drawarea, true, false, 0); } static Gtk::Button *del = 0, *cancel = 0; if (!del) widgets->get_widget("button_record_delete", del); if (!cancel) widgets->get_widget("button_record_cancel", cancel); del->set_sensitive(action_list->has_stroke(row[cols.id])); OnStroke ps(this, dialog, row); sigc::connection sig = drawarea->stroke_recorded.connect(sigc::mem_fun(ps, &OnStroke::run)); dialog->show_all(); cancel->grab_focus(); int response = dialog->run(); dialog->hide(); input_inhibitor_ungrab(); sig.disconnect(); drawarea->clear(); if (response != 1) return; action_list->set_stroke(row[cols.id], Stroke()); update_row(row); on_selection_changed(); update_actions(); } void Actions::on_selection_changed() { int n = tv.get_selection()->count_selected_rows(); button_record->set_sensitive(n == 1); button_delete->set_sensitive(n >= 1); bool resettable = false; if (n) { std::vector paths = tv.get_selection()->get_selected_rows(); for (std::vector::iterator i = paths.begin(); i != paths.end(); ++i) { Gtk::TreeRow row(*tm->get_iter(*i)); if (action_list->resettable(row[cols.id])) { resettable = true; break; } } } button_reset_actions->set_sensitive(resettable); } void Actions::on_button_new() { editing_new = true; stroke_id before = 0; if (tv.get_selection()->count_selected_rows()) { std::vector paths = tv.get_selection()->get_selected_rows(); Gtk::TreeIter i = tm->get_iter(paths[paths.size()-1]); i++; if (i != tm->children().end()) before = (*i)[cols.id]; } Gtk::TreeModel::Row row = *(tm->append()); stroke_id id = actions.add_stroke(action_list, StrokeInfo(Command::create("")), before); row[cols.id] = id; std::string name; if (action_list != actions.get_root()) name = action_list->name + " "; name += Glib::ustring::compose(_("Gesture %1"), actions.count_owned_strokes(action_list)); action_list->set_name(id, name); update_row(row); focus(id, 1, true); update_actions(); update_counts(); } void Actions::focus(stroke_id id, int col, bool edit) { editing = false; Gtk::TreeViewColumn *col1 = tv.get_column(col); Glib::signal_idle().connect([this, id, col1, edit] () { if (!editing) { Gtk::TreeModel::Children chs = tm->children(); for (Gtk::TreeIter i = chs.begin(); i != chs.end(); ++i) if (id == (*i)[cols.id]) { tv.set_cursor(Gtk::TreePath(*i), *col1, edit); } } return false; }); } void Actions::on_name_edited(const Glib::ustring& path, const Glib::ustring& new_text) { Gtk::TreeRow row(*tm->get_iter(path)); StrokeRow si = action_list->get_info(row[cols.id], false); if(new_text != *si.name) { action_list->set_name(row[cols.id], new_text); update_actions(); update_row(row); } focus(row[cols.id], 2, editing_new); } void Actions::on_text_edited(const gchar *path, const gchar *new_text) { Gtk::TreeRow row(*tm->get_iter(path)); Type type = from_name(row[cols.type]); bool changed = true; const Action* action = action_list->get_info(row[cols.id]).action; if (type == Type::COMMAND) { auto cmd = dynamic_cast(action); if(cmd && new_text != cmd->get_cmd()) action_list->set_action(row[cols.id], Command::create(new_text)); else changed = false; } else if (type == Type::TEXT) { auto text = dynamic_cast(action); if(text && new_text != text->get_text()) action_list->set_action(row[cols.id], SendText::create(new_text)); else changed = false; } else if (type == Type::PLUGIN) { auto plugin = dynamic_cast(action); if(plugin && new_text != plugin->get_action()) action_list->set_action(row[cols.id], Plugin::create(new_text)); else changed = false; } else return; if(changed) { update_row(row); update_actions(); } } void Actions::on_accel_edited(const gchar *path_string, guint accel_key, GdkModifierType mods) { uint32_t accel_mods = KeyCodes::convert_modifier(mods); Gtk::TreeRow row(*tm->get_iter(path_string)); Type type = from_name(row[cols.type]); if (type == Type::KEY) { auto send_key = SendKey::create(accel_key, accel_mods); Glib::ustring str = get_action_label(send_key.get()); if (row[cols.arg] == str) return; action_list->set_action(row[cols.id], std::move(send_key)); } else if (type == Type::SCROLL) { auto scroll = Scroll::create(accel_mods); Glib::ustring str = get_action_label(scroll.get()); if (row[cols.arg] == str) return; action_list->set_action(row[cols.id], std::move(scroll)); } else if (type == Type::IGNORE) { auto ignore = Ignore::create(accel_mods); Glib::ustring str = get_action_label(ignore.get()); if (row[cols.arg] == str) return; action_list->set_action(row[cols.id], std::move(ignore)); } else return; update_row(row); update_actions(); } void Actions::on_combo_edited(const gchar *path_string, guint item) { std::unique_ptr action; Gtk::TreeRow row(*tm->get_iter(path_string)); Type type = from_name(row[cols.type]); switch(type) { case Type::GLOBAL: action = Global::create((Global::Type)item); break; case Type::VIEW: action = View::create((View::Type)item); break; default: return; } Glib::ustring str = get_action_label(action.get()); if (row[cols.arg] == str) return; action_list->set_action(row[cols.id], std::move(action)); update_row(row); update_actions(); } void Actions::on_arg_editing_started(G_GNUC_UNUSED GtkCellEditable *editable, G_GNUC_UNUSED const gchar *path) { tv.grab_focus(); Gtk::TreeRow row(*tm->get_iter(path)); Type type = from_name(row[cols.type]); if(type == Type::COMMAND) { if(chooser.run(row[cols.name], row[cols.custom_command] ? row[cols.cmd_path] : Glib::ustring())) { auto icon_theme = Gtk::IconTheme::get_default(); if(chooser.custom_res) { action_list->set_action(row[cols.id], Command::create(chooser.res_cmdline)); update_row(row); update_actions(); } else { auto selected_app_desktop = dynamic_cast(chooser.res_app.get()); std::string desktop = selected_app_desktop ? selected_app_desktop->get_filename() : std::string(); if(!desktop.empty()) { auto cmd = Command::create(chooser.res_cmdline, desktop); CommandInfo info; info.name = selected_app_desktop->get_name(); auto icon_theme = Gtk::IconTheme::get_default(); auto icon_info = icon_theme->lookup_icon(selected_app_desktop->get_icon(), 32, Gtk::ICON_LOOKUP_FORCE_SIZE); auto pb = icon_info.load_icon(); if(pb) info.icon = pb; command_info[desktop] = std::move(info); action_list->set_action(row[cols.id], std::move(cmd)); } else action_list->set_action(row[cols.id], Command::create(chooser.res_cmdline)); update_row(row); update_actions(); } } } if (type == Type::BUTTON) { Button* bt = dynamic_cast(action_list->get_stroke_action(row[cols.id])); Button tmp; SelectButton sb(bt ? *bt : tmp, widgets); if (!sb.run()) return; action_list->set_action(row[cols.id], Button::create(Gdk::ModifierType(sb.state), sb.button)); update_row(row); update_actions(); } if (type == Type::TOUCHPAD) { Touchpad* tp = dynamic_cast(action_list->get_stroke_action(row[cols.id])); Touchpad tmp; SelectTouchpad stp(tp ? *tp : tmp, widgets, row[cols.name]); if (!stp.run()) return; auto t = stp.get_type(); action_list->set_action(row[cols.id], Touchpad::create(t, (t == Touchpad::Type::SCROLL) ? 2 : stp.get_fingers(), Gdk::ModifierType(stp.state))); update_row(row); update_actions(); } } void Actions::load_command_infos_r(ActionListDiff& x) { x.visit_all_actions([this] (const Action* a) { const Command* c = dynamic_cast(a); if(c && !c->desktop_file.empty() && !command_info.count(c->desktop_file)) { auto dinfo = Gio::DesktopAppInfo::create_from_filename(c->desktop_file); if(dinfo) { CommandInfo info; info.name = dinfo->get_name(); auto icon_theme = Gtk::IconTheme::get_default(); auto icon_info = icon_theme->lookup_icon(dinfo->get_icon(), 32, Gtk::ICON_LOOKUP_FORCE_SIZE); auto pb = icon_info.load_icon(); if(pb) info.icon = pb; command_info[c->desktop_file] = std::move(info); } } }); for(auto& y : x) load_command_infos_r(y); } void Actions::load_command_infos() { // go through all Command actions and load the name and icon of the command if possible load_command_infos_r(*actions.get_root()); } void Actions::save_actions() { if(save_error) return; try { actions.write(config_dir + ActionDB::wstroke_actions_versions[0]); } catch (std::exception &e) { save_error = true; fprintf(stderr, _("Error: Couldn't save action database: %s.\n"), e.what()); auto error_message = Glib::ustring::compose(_( "Couldn't save %1. Your changes will be lost. " "Make sure that \"%2\" is a directory and that you have write access to it. " "You can change the configuration directory " "using the XDG_CONFIG_HOME environment variable."), _("actions"), config_dir); Gtk::MessageDialog dialog(error_message, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); if(main_win->get_mapped()) dialog.set_transient_for(*main_win); dialog.show(); dialog.run(); if(!exiting) { auto app = Gtk::Application::get_default(); if(app) app->quit(); else Gtk::Main::quit(); } } } void Actions::try_import() { fprintf(stderr, "Import target: %s\n", import_file_chooser->get_filename().c_str()); ActionDB tmp_db; bool read = false; try { read = tmp_db.read(import_file_chooser->get_filename()); } catch(std::exception& e) { fprintf(stderr, "%s\n", e.what()); read = false; } if(read) { tm->clear(); apps_model->clear(); if(import_add->get_active()) actions.merge_actions(std::move(tmp_db)); else actions.overwrite_actions(std::move(tmp_db)); command_info.clear(); // we reload names and icons in case the list of installed apps changed load_command_infos(); update_action_list(); load_app_list(apps_model->children(), actions.get_root()); update_counts(); } import_dialog->close(); save_actions(); } void Actions::try_export() { auto fc = Gtk::FileChooserNative::create(_("Save strokes"), *main_win, Gtk::FileChooserAction::FILE_CHOOSER_ACTION_SAVE, _("Save"), _("Cancel")); if(fc->run() == Gtk::RESPONSE_ACCEPT) { fprintf(stderr, "Trying to save actions to %s\n", fc->get_filename().c_str()); try { actions.write(fc->get_filename()); } catch(std::exception& e) { fprintf(stderr, "%s\n", e.what()); } } } SelectButton::SelectButton(const Button& bt, Glib::RefPtr& widgets_) : widgets(widgets_) { widgets->get_widget("dialog_select", dialog); dialog->set_message(_("Select a Mouse or Pen Button")); dialog->set_secondary_text(_("Please place your mouse or pen in the box below and press the button that you want to select. You can also hold down additional modifiers.")); widgets->get_widget("eventbox", eventbox); widgets->get_widget("toggle_shift", toggle_shift); widgets->get_widget("toggle_alt", toggle_alt); widgets->get_widget("toggle_control", toggle_control); widgets->get_widget("toggle_super", toggle_super); Gtk::Bin *box_button; widgets->get_widget("box_button", box_button); select_button = dynamic_cast(box_button->get_child()); if (!select_button) { select_button = Gtk::manage(new Gtk::ComboBoxText); box_button->add(*select_button); for (int i = 1; i <= 12; i++) select_button->append(Glib::ustring::compose(_("Button %1"), i)); select_button->show(); } select_button->set_active(bt.get_button()-1); toggle_shift->set_active(bt.get_button() && (bt.get_mods() & GDK_SHIFT_MASK)); toggle_control->set_active(bt.get_button() && (bt.get_mods() & GDK_CONTROL_MASK)); toggle_alt->set_active(bt.get_button() && (bt.get_mods() & GDK_MOD1_MASK)); toggle_super->set_active(bt.get_button() && (bt.get_mods() & GDK_SUPER_MASK)); if (!eventbox->get_children().size()) { eventbox->set_events(Gdk::BUTTON_PRESS_MASK); Glib::RefPtr pb = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB,true,8,400,200); pb->fill(0x808080ff); Gtk::Image& box = *Gtk::manage(new Gtk::Image(pb)); eventbox->add(box); box.show(); } handler = eventbox->signal_button_press_event().connect(sigc::mem_fun(*this, &SelectButton::on_button_press)); } SelectButton::~SelectButton() { handler.disconnect(); } bool SelectButton::run() { dialog->show(); Gtk::Button *select_ok; widgets->get_widget("select_ok", select_ok); select_ok->grab_focus(); int response; do { response = dialog->run(); } while (!response); dialog->hide(); switch (response) { case 1: // Okay button = select_button->get_active_row_number() + 1; if (!button) return false; state = 0; if (toggle_shift->get_active()) state |= GDK_SHIFT_MASK; if (toggle_control->get_active()) state |= GDK_CONTROL_MASK; if (toggle_alt->get_active()) state |= GDK_MOD1_MASK; if (toggle_super->get_active()) state |= GDK_SUPER_MASK; return true; case 2: // Default button = 0; state = 0; return true; case 3: // Click - all the work has already been done return true; case -1: // Cancel default: // Something went wrong return false; } } bool SelectButton::on_button_press(GdkEventButton *ev) { button = ev->button; state = ev->state; if (state & Mod4Mask) state |= GDK_SUPER_MASK; state &= gtk_accelerator_get_default_mod_mask(); dialog->response(3); return true; } SelectTouchpad::SelectTouchpad(const Touchpad& bt, Glib::RefPtr& widgets_, const Glib::ustring& action_name) : widgets(widgets_) { widgets->get_widget("dialog_touchpad", dialog); Gtk::HeaderBar *header; widgets->get_widget("header_touchpad", header); widgets->get_widget("touchpad_toggle_shift", toggle_shift); widgets->get_widget("touchpad_toggle_alt", toggle_alt); widgets->get_widget("touchpad_toggle_control", toggle_control); widgets->get_widget("touchpad_toggle_super", toggle_super); widgets->get_widget("touchpad_type_scroll", radio_scroll); widgets->get_widget("touchpad_type_swipe", radio_swipe); widgets->get_widget("touchpad_type_pinch", radio_pinch); widgets->get_widget("touchpad_fingers", spin_fingers); /* Note: a GtkAdjustment is not a GtkWidget, so the get_widget function * cannot be used to retrieve it. Also, widgets holds a reference to it, * so we don't need to keep the RefPtr. */ Glib::RefPtr adj_obj = widgets->get_object("touchpad_fingers_adj"); adj_fingers = dynamic_cast(adj_obj.get()); if(!adj_fingers) throw std::runtime_error("Error loading UI!\n"); Glib::ustring str = Glib::ustring::compose(_("Set properties for action %1"), action_name); header->set_subtitle(str); toggle_shift->set_active(bt.get_mods() & GDK_SHIFT_MASK); toggle_control->set_active(bt.get_mods() & GDK_CONTROL_MASK); toggle_alt->set_active(bt.get_mods() & GDK_MOD1_MASK); toggle_super->set_active(bt.get_mods() & GDK_SUPER_MASK); adj_fingers->set_value(bt.fingers); switch(bt.get_action_type()) { // note: NONE is the default when a new action is added case Touchpad::Type::NONE: case Touchpad::Type::SCROLL: radio_scroll->set_active(true); spin_fingers->set_sensitive(false); break; case Touchpad::Type::PINCH: radio_pinch->set_active(true); spin_fingers->set_sensitive(true); break; case Touchpad::Type::SWIPE: radio_swipe->set_active(true); spin_fingers->set_sensitive(true); break; } } bool SelectTouchpad::run() { dialog->show(); Gtk::Button *select_ok; widgets->get_widget("touchpad_select_ok", select_ok); select_ok->grab_focus(); int response; do { response = dialog->run(); } while (!response); dialog->hide(); switch (response) { case Gtk::RESPONSE_OK: // Okay state = 0; if (toggle_shift->get_active()) state |= GDK_SHIFT_MASK; if (toggle_control->get_active()) state |= GDK_CONTROL_MASK; if (toggle_alt->get_active()) state |= GDK_MOD1_MASK; if (toggle_super->get_active()) state |= GDK_SUPER_MASK; return true; case Gtk::RESPONSE_CANCEL: // Cancel default: // Something went wrong return false; } } Touchpad::Type SelectTouchpad::get_type() const { if(radio_scroll->get_active()) return Touchpad::Type::SCROLL; if(radio_swipe->get_active()) return Touchpad::Type::SWIPE; if(radio_pinch->get_active()) return Touchpad::Type::PINCH; return Touchpad::Type::NONE; } uint32_t SelectTouchpad::get_fingers() const { uint32_t tmp = (uint32_t)adj_fingers->get_value(); return std::max(tmp, 2U); } struct ActionLabel : public ActionVisitor { protected: Glib::ustring label; public: void visit(const Command* action) override { label = action->get_cmd(); } void visit(const SendKey* action) override { uint32_t mods = action->get_mods(); mods = KeyCodes::add_virtual_modifiers(mods); uint32_t keysym = KeyCodes::convert_keycode(action->get_key()); label = Gtk::AccelGroup::get_label(keysym, static_cast(mods)); } void visit(const SendText* action) override { label = action->get_text(); } void visit(const Scroll* action) override { uint32_t mods = action->get_mods(); mods = KeyCodes::add_virtual_modifiers(mods); if(mods) label = Gtk::AccelGroup::get_label(0, static_cast(mods)) + _(" + Scroll"); else label = _("Scroll"); } void visit(const Ignore* action) override { uint32_t mods = action->get_mods(); mods = KeyCodes::add_virtual_modifiers(mods); if (mods) label = Gtk::AccelGroup::get_label(0, static_cast(mods)); else label = _("Ignore"); } void visit(const Button* action) override { label = Gtk::AccelGroup::get_label(0, (Gdk::ModifierType)action->get_mods()); label += Glib::ustring::compose(_(" + Button %1"), action->get_button()); } void visit(const Global* action) override { auto t = action->get_action_type(); label = Global::get_type_str(t); } void visit(const View* action) override { auto t = action->get_action_type(); label = View::get_type_str(t); } void visit(const Touchpad* action) override { auto t = action->get_action_type(); if(t == Touchpad::Type::NONE) { label = "None"; return; } uint32_t mods = action->get_mods(); mods = KeyCodes::add_virtual_modifiers(mods); if(mods) label = Gtk::AccelGroup::get_label(0, static_cast(mods)) + " + "; else label = ""; if(t == Touchpad::Type::SCROLL) { label += "Scroll"; return; } /* Pinch or swipe */ if(t == Touchpad::Type::PINCH) label += Glib::ustring::compose(_(" %1 finger pinch"), action->fingers); else label += Glib::ustring::compose(_(" %1 finger swipe"), action->fingers); } void visit(const Plugin* action) override { label = action->get_action(); } const Glib::ustring get_label() const { return label; } }; static Glib::ustring get_action_label(const Action* action) { if(!action) return ""; ActionLabel label; action->visit(&label); return label.get_label(); } const char* Global::types[Global::n_actions] = { N_("None"), N_("Show expo"), N_("Scale (current workspace)"), N_("Scale (all workspaces)"), N_("Configure gestures"), N_("Toggle show desktop"), N_("Rotate cube (interactive)") }; const char* Global::get_type_str(Type type) { return types[static_cast(type)]; } const char* View::types[View::n_actions] = { N_("None"), N_("Close"), N_("Toggle maximized"), N_("Move"), N_("Resize"), N_("Minimize"), N_("Toggle fullscreen"), N_("Send to back"), N_("Toggle always on top"), N_("Toggle sticky") }; const char* View::get_type_str(Type type) { return types[static_cast(type)]; } wstroke-2.2.1/src/actions.h000066400000000000000000000162731472137506600156360ustar00rootroot00000000000000/* * Copyright (c) 2008-2009, Thomas Jaeger * Copyright (c) 2020-2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __ACTIONS_H__ #define __ACTIONS_H__ #include #include #include #include "actiondb.h" #include "appchooser.h" class TreeViewMulti : public Gtk::TreeView { bool pending; Gtk::TreePath path; virtual bool on_button_press_event(GdkEventButton* event); virtual bool on_button_release_event(GdkEventButton* event); virtual void on_drag_begin(const Glib::RefPtr &context); public: TreeViewMulti(); }; class Actions { public: Actions(const std::string& config_dir_, Glib::RefPtr& widgets_) : chooser(widgets_), widgets(widgets_), config_dir(config_dir_) { } void startup(Gtk::Application* app, Gtk::Dialog* message_dialog = nullptr); private: void on_button_delete(); void on_button_new(); void on_selection_changed(); void on_name_edited(const Glib::ustring& path, const Glib::ustring& new_text); void on_type_edited(const Glib::ustring& path, const Glib::ustring& new_text); void on_row_activated(Gtk::TreeRow& row); void on_cell_data_name(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter); void on_cell_data_type(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter); void save_actions(); void update_actions() { actions_changed = true; } public: void on_accel_edited(const gchar *path_string, guint accel_key, GdkModifierType accel_mods); void on_combo_edited(const gchar *path_string, guint item); void on_arg_editing_started(GtkCellEditable *editable, const gchar *path); void on_text_edited(const gchar *path, const gchar *new_text); void on_cell_data_arg(GtkCellRenderer *cell, gchar *path); void on_stroke_editing(const char* path); Gtk::Window* get_main_win() { return main_win.get(); } void exit() { exiting = true; save_actions(); } ActionDB actions; private: int compare_ids(const Gtk::TreeModel::iterator &a, const Gtk::TreeModel::iterator &b); class OnStroke; void focus(stroke_id id, int col, bool edit); void on_add_app(); void on_add_group(); void on_apps_selection_changed(); void load_app_list(const Gtk::TreeNodeChildren &ch, ActionListDiff *actions); void update_action_list(); void update_row(const Gtk::TreeRow& row); void update_counts(); void on_remove_app(); bool select_exclude_row(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter, const std::string& name); void on_add_exclude(); void on_remove_exclude(); class ModelColumns : public Gtk::TreeModel::ColumnRecord { public: ModelColumns() { add(stroke); add(name); add(type); add(arg); add(cmd_save); add(id); add(name_bold); add(action_bold); add(deactivated); add(action_icon); add(cmd_path); add(custom_command); } Gtk::TreeModelColumn > stroke, action_icon; Gtk::TreeModelColumn name, type, arg, cmd_save, plugin_action_save, cmd_path; Gtk::TreeModelColumn id; Gtk::TreeModelColumn name_bold, action_bold; Gtk::TreeModelColumn deactivated, custom_command; }; class Store : public Gtk::ListStore { Actions *parent; public: Store(const Gtk::TreeModelColumnRecord &columns, Actions *p) : Gtk::ListStore(columns), parent(p) {} static Glib::RefPtr create(const Gtk::TreeModelColumnRecord &columns, Actions *parent) { return Glib::RefPtr(new Store(columns, parent)); } protected: bool row_draggable_vfunc(const Gtk::TreeModel::Path&) const; bool row_drop_possible_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData&) const; bool drag_data_received_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData& selection); }; class AppsStore : public Gtk::TreeStore { Actions *parent; public: AppsStore(const Gtk::TreeModelColumnRecord &columns, Actions *p) : Gtk::TreeStore(columns), parent(p) {} static Glib::RefPtr create(const Gtk::TreeModelColumnRecord &columns, Actions *parent) { return Glib::RefPtr(new AppsStore(columns, parent)); } protected: bool row_drop_possible_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData &selection) const; bool drag_data_received_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData& selection); }; ModelColumns cols; TreeViewMulti tv; Glib::RefPtr tm; /* Special casing to store additional info about Command actions */ struct CommandInfo { Glib::ustring name; Glib::RefPtr icon; }; std::unordered_map command_info; void load_command_infos_r(ActionListDiff& x); void load_command_infos(); /* helper for the app chooser */ AppChooser chooser; Gtk::TreeView *apps_view = nullptr; Glib::RefPtr apps_model; /* helper to find a given app in apps_view / apps_model */ bool get_action_item(const ActionListDiff* x, Gtk::TreeIter& it); class Single : public Gtk::TreeModel::ColumnRecord { public: Single() { add(type); } Gtk::TreeModelColumn type; }; Single type; class Apps : public Gtk::TreeModel::ColumnRecord { public: Apps() { add(app); add(actions); add(count); } Gtk::TreeModelColumn app; Gtk::TreeModelColumn*> actions; Gtk::TreeModelColumn count; }; Apps ca; /* exception list */ Single exclude_cols; Glib::RefPtr exclude_tm; Gtk::TreeView* exclude_tv; struct Focus; Glib::RefPtr type_store; Gtk::Button *button_record, *button_delete, *button_remove_app, *button_reset_actions; Gtk::CheckButton *check_show_deleted; Gtk::Expander *expander_apps; Gtk::VPaned *vpaned_apps; int vpaned_position; bool editing_new = false; bool editing = false; ActionListDiff* action_list; Glib::RefPtr widgets; /* import / export */ Gtk::Window* import_dialog; Gtk::Button* button_import_cancel; Gtk::Button* button_import_import; Gtk::FileChooserButton* import_file_chooser; Gtk::RadioButton* import_add; Gtk::InfoBar* import_info; Gtk::Label* import_info_label; void try_import(); void try_export(); /* main window */ std::unique_ptr main_win; const std::string config_dir; Glib::RefPtr timeout; /* timeout for saving changes */ bool actions_changed = false; bool exiting = false; bool save_error = false; }; #endif wstroke-2.2.1/src/appchooser.cc000066400000000000000000000144661472137506600165010ustar00rootroot00000000000000/* * Copyright (c) 2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "appchooser.h" #include AppChooser::AppBox::AppBox(Glib::RefPtr& app_) : Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL), app(app_) { auto image = new Gtk::Image(app->get_icon(), Gtk::IconSize(Gtk::ICON_SIZE_DIALOG)); image->set_pixel_size(48); this->add(*image); const std::string& name = app->get_name(); auto label = new Gtk::Label(name.length() > 23 ? name.substr(0, 20) + "..." : name); this->add(*label); name_lower = Glib::ustring(app->get_name()).lowercase(); } void AppChooser::update_apps() { bool tmp; mutex.lock(); tmp = thread_running; if(tmp) more_work = true; mutex.unlock(); if(!tmp) { if(thread.joinable()) thread.join(); thread_running = true; more_work = false; thread = std::thread(&AppChooser::thread_func, this); } update_pending = false; } void AppChooser::thread_func() { while(true) { if(exit_request.load()) break; AppContent* tmp = new AppContent(); tmp->apps = Gio::AppInfo::get_all(); if(exit_request.load()) { delete tmp; break; } auto it = std::remove_if(tmp->apps.begin(), tmp->apps.end(), [](const Glib::RefPtr& p) { return !p->should_show(); }); tmp->apps.erase(it, tmp->apps.end()); tmp->flowbox.reset(new Gtk::FlowBox()); for(auto& a : tmp->apps) { if(exit_request.load()) break; auto box = new AppBox(a); tmp->flowbox->add(*box); } std::lock_guard lock(mutex); tmp = apps_pending.exchange(tmp); if(tmp) delete tmp; if(!more_work) { thread_running = false; break; } more_work = false; } } void on_apps_changed(GAppInfoMonitor*, void* p) { AppChooser* chooser = (AppChooser*)p; if(chooser->update_pending) return; chooser->update_pending = true; Glib::signal_timeout().connect_seconds_once([chooser](){ chooser->update_apps(); }, 4); } void AppChooser::startup() { update_apps(); monitor = g_app_info_monitor_get(); g_signal_connect(monitor, "changed", G_CALLBACK(on_apps_changed), this); /* basic setup for the GUI */ widgets->get_widget("dialog_appchooser", dialog); widgets->get_widget("header_appchooser", header); widgets->get_widget("entry_appchooser", entry); widgets->get_widget("checkbutton_appchooser", cb); widgets->get_widget("scrolledwindow_appchooser", sw); widgets->get_widget("appchooser_ok", select_ok); widgets->get_widget("searchentry_appchooser", searchentry); cb->signal_toggled().connect([this]() { entry->set_sensitive(cb->get_active()); }); searchentry->signal_search_changed().connect([this]() { filter_lower = searchentry->get_text().lowercase(); if(apps) apps->flowbox->invalidate_filter(); }); searchentry->signal_stop_search().connect([this]() { searchentry->set_text(Glib::ustring()); }); } int AppChooser::apps_sort(const Gtk::FlowBoxChild* x, const Gtk::FlowBoxChild* y) { const AppBox* a = dynamic_cast(x->get_child()); const AppBox* b = dynamic_cast(y->get_child()); if(!(a && b)) return 0; // or throw an exception? return a->compare(*b); } bool AppChooser::apps_filter(const Gtk::FlowBoxChild* x) const { if(filter_lower.empty()) return true; const AppBox* a = dynamic_cast(x->get_child()); return a && a->filter(filter_lower); } bool AppChooser::run(const Glib::ustring& gesture_name, const Glib::ustring& custom_command) { if(!apps && !apps_pending) thread.join(); // in this case, the worker thread should be running AppContent* tmp = nullptr; tmp = apps_pending.exchange(tmp); if(tmp) { sw->remove(); apps.reset(tmp); apps->flowbox->signal_child_activated().connect([this](Gtk::FlowBoxChild*) { dialog->response(Gtk::RESPONSE_OK); }); apps->flowbox->set_valign(Gtk::ALIGN_START); apps->flowbox->set_homogeneous(true); apps->flowbox->set_activate_on_single_click(false); apps->flowbox->set_sort_func(&apps_sort); apps->flowbox->set_filter_func([this](const Gtk::FlowBoxChild* x) { return apps_filter(x); }); sw->add(*apps->flowbox); } if(!apps) return false; if(custom_command.empty()) { cb->set_active(false); entry->set_sensitive(false); } else { cb->set_active(true); entry->set_sensitive(true); } entry->set_text(custom_command); select_ok->grab_default(); Glib::ustring str = Glib::ustring::compose(_("Choose app to run for gesture %1"), gesture_name); header->set_subtitle(str); dialog->show_all(); auto x = dialog->run(); dialog->hide(); if(x == Gtk::RESPONSE_OK) { if(cb->get_active()) { res_cmdline = entry->get_text(); custom_res = true; res_app.reset(); return true; } else { custom_res = false; auto tmp = apps->flowbox->get_selected_children(); if(tmp.size()) { auto selected = tmp.front(); const AppBox* box = dynamic_cast(selected->get_child()); if(box) { res_app = box->get_app(); res_cmdline = res_app->get_commandline(); // remove placeholders (%f, %F, %u and %U for files, and misc; note: in theory, we should properly parse %i, %c and %k) auto i = res_cmdline.begin(); for(auto j = res_cmdline.begin(); j != res_cmdline.end(); ++j) { if(*j == '%') { ++j; if(j == res_cmdline.end()) break; if(*j != '%') continue; } if(j != i) *i = *j; ++i; } res_cmdline.erase(i, res_cmdline.end()); return true; } } } } res_app.reset(); res_cmdline = ""; custom_res = false; return false; } AppChooser::~AppChooser() { if(monitor) g_object_unref(monitor); exit_request.store(true); if(thread.joinable()) thread.join(); AppContent* tmp = apps_pending; if(tmp) delete tmp; sw->remove(); } wstroke-2.2.1/src/appchooser.h000066400000000000000000000051611472137506600163330ustar00rootroot00000000000000/* * Copyright (c) 2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef APPCHOOSER_H #define APPCHOOSER_H #include #include #include #include #include class AppChooser { private: Glib::RefPtr widgets; Gtk::Dialog* dialog; Gtk::HeaderBar* header; Gtk::ScrolledWindow* sw; Gtk::SearchEntry* searchentry; Gtk::Entry* entry; Gtk::CheckButton* cb; Gtk::Button *select_ok; Glib::ustring filter_lower; GAppInfoMonitor* monitor = nullptr; class AppBox : public Gtk::Box { private: Glib::RefPtr app; Glib::ustring name_lower; public: AppBox(Glib::RefPtr& app_); Glib::RefPtr get_app() const { return app; } bool filter(const Glib::ustring& filter) const { return name_lower.find(filter) != name_lower.npos; } // filter already lowercase int compare(const AppBox& box) const { return name_lower.compare(box.name_lower); } }; struct AppContent { std::vector > apps; std::unique_ptr flowbox; }; std::unique_ptr apps; std::atomic apps_pending; std::thread thread; std::mutex mutex; bool thread_running = false; bool more_work = false; std::atomic exit_request{false}; bool first_run = false; bool update_pending = false; void update_apps(); void thread_func(); friend void on_apps_changed(GAppInfoMonitor*, void* p); static int apps_sort(const Gtk::FlowBoxChild* x, const Gtk::FlowBoxChild* y); bool apps_filter(const Gtk::FlowBoxChild* x) const; public: Glib::RefPtr res_app; std::string res_cmdline; bool custom_res = false; AppChooser(Glib::RefPtr& w) : widgets(w) { } ~AppChooser(); void startup(); bool run(const Glib::ustring& gesture_name, const Glib::ustring& custom_command); }; #endif wstroke-2.2.1/src/cellrenderertextish.vala000066400000000000000000000136471472137506600207530ustar00rootroot00000000000000/* compile with valac -c cellrenderertextish.vala --pkg gtk+-3.0 --vapidir . --pkg input_inhibitor -C -H cellrenderertextish.h */ public class CellRendererTextish : Gtk.CellRendererText { public enum Mode { Text, Key, Popup, Combo } public new Mode mode; public unowned string[] items; public Gdk.Pixbuf? icon { get; set; default = null; } public signal void key_edited(string path, Gdk.ModifierType mods, uint code); public signal void combo_edited(string path, uint row); private Gtk.CellEditable? cell; public CellRendererTextish() { mode = Mode.Text; cell = null; items = null; } public CellRendererTextish.with_items(string[] items) { mode = Mode.Text; cell = null; this.items = items; } public void set_items(string[] items_) { items = items_; } public override unowned Gtk.CellEditable? start_editing(Gdk.Event? event, Gtk.Widget widget, string path, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gtk.CellRendererState flags) { cell = null; if (!editable) return cell; switch (mode) { case Mode.Text: cell = base.start_editing(event, widget, path, background_area, cell_area, flags); break; case Mode.Key: cell = new CellEditableAccel(this, path, widget); break; case Mode.Combo: cell = new CellEditableCombo(this, path, widget, items); break; case Mode.Popup: cell = new CellEditableDummy(); break; } return cell; } public override void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gtk.CellRendererState flags) { Gdk.cairo_rectangle(ctx, cell_area); if(icon != null) { Gdk.cairo_set_source_pixbuf(ctx, icon, cell_area.x, cell_area.y + cell_area.height / 2 - icon.height / 2); ctx.fill(); cell_area.x += icon.width + 4; } base.render(ctx, widget, background_area, cell_area, flags); } public override void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset, out int y_offset, out int width, out int height) { base.get_size(widget, cell_area, out x_offset, out y_offset, out width, out height); if(icon != null) { width += icon.width; height = int.max(height, icon.height); } } public override void get_preferred_height(Gtk.Widget widget, out int minimum_size, out int natural_size) { base.get_preferred_height(widget, out minimum_size, out natural_size); if(icon != null) { minimum_size = int.max(minimum_size, icon.height); natural_size = int.max(natural_size, icon.height); } } public override void get_preferred_height_for_width(Gtk.Widget widget, int width, out int minimum_height, out int natural_height) { base.get_preferred_height_for_width(widget, width, out minimum_height, out natural_height); if(icon != null) { minimum_height = int.max(minimum_height, icon.height); natural_height = int.max(natural_height, icon.height); } } public override void get_preferred_width(Gtk.Widget widget, out int minimum_size, out int natural_size) { base.get_preferred_width(widget, out minimum_size, out natural_size); if(icon != null) { minimum_size += icon.width; natural_size += icon.width; } } public override void get_preferred_width_for_height(Gtk.Widget widget, int height, out int minimum_width, out int natural_width) { base.get_preferred_width_for_height(widget, height, out minimum_width, out natural_width); if(icon != null) { minimum_width += icon.width; natural_width += icon.width; } } } class CellEditableDummy : Gtk.EventBox, Gtk.CellEditable { public bool editing_canceled { get; set; } protected virtual void start_editing(Gdk.Event? event) { editing_done(); remove_widget(); } } class CellEditableAccel : Gtk.EventBox, Gtk.CellEditable { public bool editing_canceled { get; set; } new CellRendererTextish parent; new string path; public CellEditableAccel(CellRendererTextish parent, string path, Gtk.Widget widget) { this.parent = parent; this.path = path; editing_done.connect(on_editing_done); Gtk.Label label = new Gtk.Label("Key combination..."); label.set_alignment(0.0f, 0.5f); add(label); override_background_color(Gtk.StateFlags.NORMAL, widget.get_style_context().get_background_color(Gtk.StateFlags.SELECTED)); label.override_color(Gtk.StateFlags.NORMAL, widget.get_style_context().get_color(Gtk.StateFlags.SELECTED)); show_all(); } protected virtual void start_editing(Gdk.Event? event) { Gtk.grab_add(this); Gdk.keyboard_grab(get_window(), false, event != null ? event.get_time() : Gdk.CURRENT_TIME); Inhibitor.grab(); /* Gdk.DeviceManager dm = get_window().get_display().get_device_manager(); foreach (Gdk.Device dev in dm.list_devices(Gdk.DeviceType.SLAVE)) Gtk.device_grab_add(this, dev, true); */ key_press_event.connect(on_key); } bool on_key(Gdk.EventKey event) { if (event.is_modifier != 0) return true; switch (event.keyval) { case Gdk.Key.Super_L: case Gdk.Key.Super_R: case Gdk.Key.Hyper_L: case Gdk.Key.Hyper_R: return true; } Gdk.ModifierType mods = event.state; /* & Gtk.accelerator_get_default_mod_mask(); -- does not work! */ editing_done(); remove_widget(); parent.key_edited(path, mods, event.hardware_keycode); return true; } void on_editing_done() { Gtk.grab_remove(this); Gdk.keyboard_ungrab(Gdk.CURRENT_TIME); Inhibitor.ungrab(); /* Gdk.DeviceManager dm = get_window().get_display().get_device_manager(); foreach (Gdk.Device dev in dm.list_devices(Gdk.DeviceType.SLAVE)) Gtk.device_grab_remove(this, dev); */ } } class CellEditableCombo : Gtk.ComboBoxText, Gtk.CellEditable { new CellRendererTextish parent; new string path; public CellEditableCombo(CellRendererTextish parent, string path, Gtk.Widget widget, string[] items) { this.parent = parent; this.path = path; foreach (string item in items) { append_text(item); } changed.connect(() => parent.combo_edited(path, active)); } public virtual void start_editing(Gdk.Event? event) { base.start_editing(event); show_all(); } } wstroke-2.2.1/src/config.h.in000066400000000000000000000001111472137506600160300ustar00rootroot00000000000000#ifndef CONFIG_H #define CONFIG_H #define DATA_DIR "@DATA_DIR@" #endif wstroke-2.2.1/src/convert_keycodes.cc000066400000000000000000000062421472137506600176750ustar00rootroot00000000000000/* * convert_keycodes.cc * * Copyright 2020 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "convert_keycodes.h" #include extern "C" { #include } GdkKeymap* KeyCodes::keymap = nullptr; unsigned int KeyCodes::keycode_errors = 0; void KeyCodes::init() { if(keymap) return; GdkDisplay* dpy = gdk_display_get_default(); keymap = gdk_keymap_get_for_display(dpy); } static constexpr std::array, 10> modifier_match = { std::pair(GDK_SHIFT_MASK, WLR_MODIFIER_SHIFT), std::pair(GDK_LOCK_MASK, WLR_MODIFIER_CAPS), std::pair(GDK_CONTROL_MASK, WLR_MODIFIER_CTRL), std::pair(GDK_MOD1_MASK, WLR_MODIFIER_ALT), std::pair(GDK_META_MASK, WLR_MODIFIER_ALT), std::pair(GDK_MOD2_MASK, WLR_MODIFIER_MOD2), std::pair(GDK_MOD3_MASK, WLR_MODIFIER_MOD3), std::pair(GDK_MOD5_MASK, WLR_MODIFIER_MOD5), std::pair(GDK_MOD4_MASK, WLR_MODIFIER_LOGO), std::pair(GDK_SUPER_MASK, WLR_MODIFIER_LOGO) }; uint32_t KeyCodes::convert_modifier(uint32_t mod) { uint32_t ret = 0; for(auto p : modifier_match) if(mod & p.first) ret |= p.second; return ret; } uint32_t KeyCodes::convert_keysym(uint32_t key) { if(!keymap) return 0; uint32_t ret = 0; GdkKeymapKey* keys = nullptr; gint n_keys = 0; if(gdk_keymap_get_entries_for_keyval(keymap, key, &keys, &n_keys) && n_keys && keys) { for(gint i = 0; i < n_keys; i++) { if(keys[i].group == 0 || keys[i].level == 0) { ret = keys[i].keycode; break; } } } if(keys) g_free(keys); if(!ret) { keycode_errors++; fprintf(stderr, "KeyCodes::convert_keysym(): could not convert %u\n", key); } return ret; } uint32_t KeyCodes::convert_keycode(uint32_t code) { if(!keymap) return 0; GdkKeymapKey key; key.keycode = code; key.level = 0; key.group = 0; return gdk_keymap_lookup_key(keymap, &key); } uint32_t KeyCodes::add_virtual_modifiers(uint32_t mod) { /* currently this only takes care of super * additional logic might be needed on e.g. Apple keyboards */ if(mod & WLR_MODIFIER_LOGO) { mod ^= WLR_MODIFIER_LOGO; mod |= GDK_SUPER_MASK; } return mod; } wstroke-2.2.1/src/convert_keycodes.h000066400000000000000000000033261472137506600175370ustar00rootroot00000000000000/* * convert_keycodes.h * * Copyright 2020 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include #include #ifndef CONVERT_KEYCODES_H #define CONVERT_KEYCODES_H class KeyCodes { public: /* convert from a combination of Gdk modifier constants to the * WLR modifier enum constants (take care of "virtual" modifiers * like SUPER, ALT, etc.) */ static uint32_t convert_modifier(uint32_t mod); /* add back "virtual" modifiers -- calls the corresponding GDK function */ static uint32_t add_virtual_modifiers(uint32_t mod); /* try to convert a keysym to a hardware keycode; returns the * keycode or zero if it was not found */ static uint32_t convert_keysym(uint32_t key); /* convert a hardware keycode to a keysym (using level = 0 and * group = 0) for the purpose of displaying it to the user */ static uint32_t convert_keycode(uint32_t code); static void init(); static unsigned int keycode_errors; protected: static GdkKeymap* keymap; }; #endif wstroke-2.2.1/src/easystroke_gestures.cpp000066400000000000000000001017641472137506600206430ustar00rootroot00000000000000/* * Copyright (c) 2020-2024, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include } #include #include "gesture.h" #include "actiondb.h" #include "input_events.hpp" static const char *default_vertex_shader_source = R"(#version 100 attribute mediump vec2 position; attribute highp vec2 uvPosition; varying highp vec2 uvpos; uniform mat4 MVP; void main() { gl_Position = MVP * vec4(position.xy, 0.0, 1.0); uvpos = uvPosition; })"; static const char *color_rect_fragment_source = R"(#version 100 varying highp vec2 uvpos; uniform mediump vec4 color; void main() { gl_FragColor = color; })"; class ws_node; class ws_render_instance : public wf::scene::simple_render_instance_t { public: /* render the current content of the overlay texture */ void render(const wf::render_target_t& target, const wf::region_t& region) override; ws_render_instance(ws_node *self, wf::scene::damage_callback push_damage, wf::output_t *output) : wf::scene::simple_render_instance_t(self, push_damage, output) { } }; /* node to draw lines on the screen, a simplified version of annotate */ class ws_node : public wf::scene::node_t { public: wf::output_t* const output; /* current annotation to be rendered -- store it in a framebuffer */ wf::framebuffer_t fb; wf::option_wrapper_t stroke_color{"wstroke/stroke_color"}; wf::option_wrapper_t stroke_width{"wstroke/stroke_width"}; OpenGL::program_t color_program; ws_node(wf::output_t* output_) : node_t(false), output(output_) { /* copied from opengl.cpp */ OpenGL::render_begin(); color_program.set_simple(OpenGL::compile_program( default_vertex_shader_source, color_rect_fragment_source)); OpenGL::render_end(); } /* allocate frambuffer for storing the drawings if it * has not been allocated yet, according to the current * output size */ bool ensure_fb() { bool ret = true; if(fb.tex == (GLuint)-1 || fb.fb == (GLuint)-1) { auto dim = output->get_screen_size(); OpenGL::render_begin(); ret = fb.allocate(dim.width, dim.height); if(ret) { fb.bind(); // bind buffer to clear it OpenGL::clear({0, 0, 0, 0}); } OpenGL::render_end(); } return ret; } static void pad_damage_rect(wf::geometry_t& damageRect, float stroke_width) { damageRect.x = std::floor(damageRect.x - stroke_width / 2.0); damageRect.y = std::floor(damageRect.y - stroke_width / 2.0); damageRect.width += std::ceil(stroke_width + 1); damageRect.height += std::ceil(stroke_width + 1); } /* draw a line into the overlay texture between the given points; * allocates the overlay texture if necessary and activates rendering */ void draw_line(int x1, int y1, int x2, int y2) { if(stroke_width == 0) return; if(!ensure_fb()) return; wf::dimensions_t dim = output->get_screen_size(); auto ortho = glm::ortho(0.0f, (float)dim.width, (float)dim.height, 0.0f); OpenGL::render_begin(fb); GL_CALL(glLineWidth((float)stroke_width)); GLfloat vertexData[4] = { (float)x1, (float)y1, (float)x2, (float)y2 }; render_vertices(vertexData, 2, stroke_color, GL_LINES, ortho); OpenGL::render_end(); wf::geometry_t d{std::min(x1,x2), std::min(y1,y2), std::abs(x1-x2), std::abs(y1-y2)}; pad_damage_rect(d, stroke_width); wf::scene::node_damage_signal ev; ev.region = d; /* note: implicit conversion to wf::region_t */ this->emit(&ev); } /* clear everything rendered by this plugin and deallocate the framebuffer */ void clear_lines() { fb.release(); output->render->damage_whole(); } /* render a sequence of vertices, using the given color * should only be called between OpenGL::render_begin() and * OpenGL::render_end() */ void render_vertices(GLfloat* vertexData, GLsizei nvertices, wf::color_t color, GLenum mode, glm::mat4 matrix) { color_program.use(wf::TEXTURE_TYPE_RGBA); color_program.attrib_pointer("position", 2, 0, vertexData); color_program.uniformMatrix4f("MVP", matrix); color_program.uniform4f("color", {color.r, color.g, color.b, color.a}); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); GL_CALL(glDrawArrays(mode, 0, nvertices)); color_program.deactivate(); } void gen_render_instances(std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *shown_on) override { if(shown_on == output) instances.push_back(std::make_unique(this, push_damage, shown_on)); } wf::geometry_t get_bounding_box() override { wf::dimensions_t dim = output->get_screen_size(); return {0, 0, dim.width, dim.height}; } }; void ws_render_instance::render(const wf::render_target_t& target, const wf::region_t& region) { if(this->self->fb.tex == (GLuint)-1) return; auto geometry = this->self->output->get_relative_geometry(); OpenGL::render_begin(target); for (auto& box : region) { target.logic_scissor(wlr_box_from_pixman_box(box)); OpenGL::render_texture(this->self->fb.tex, target, geometry); } OpenGL::render_end(); } class wstroke : public wf::per_output_plugin_instance_t, public wf::pointer_interaction_t, ActionVisitor { protected: wf::button_callback stroke_initiate; wf::option_wrapper_t initiate{"wstroke/initiate"}; wf::option_wrapper_t target_mouse{"wstroke/target_view_mouse"}; wf::option_wrapper_t focus_mode{"wstroke/focus_mode"}; wf::option_wrapper_t start_timeout{"wstroke/start_timeout"}; wf::option_wrapper_t end_timeout{"wstroke/end_timeout"}; wf::option_wrapper_t resize_edges{"wstroke/resize_edges"}; wf::option_wrapper_t touchpad_scroll_sensitivity{"wstroke/touchpad_scroll_sensitivity"}; wf::option_wrapper_t touchpad_pinch_sensitivity{"wstroke/touchpad_pinch_sensitivity"}; std::unique_ptr input_grab; wf::plugin_activation_data_t grab_interface{ .name = "wstroke", .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, .cancel = [this]() { cancel_stroke(); } }; Stroke::PreStroke ps; std::unique_ptr actions; input_headless input; wf::wl_idle_call idle_generate; wayfire_view target_view; wayfire_view initial_active_view; wayfire_view mouse_view; int inotify_fd = -1; struct wl_event_source* inotify_source = nullptr; static constexpr size_t inotify_buffer_size = 10*(sizeof(struct inotify_event) + NAME_MAX + 1); char inotify_buffer[inotify_buffer_size]; /* true if we need to refocus the initial view after the gesture * action -- refocusing might be done by the action visitor or * the main function when ending the gesture */ bool needs_refocus = false; bool needs_refocus2 = false; /* temporary copy of the above used by the idle callbacks */ bool active = false; bool is_gesture = false; /* whether currently processing a gesture */ /* current modifier keys held by the ignore action */ uint32_t ignore_active = 0; /* currently active touchpad action */ Touchpad::Type touchpad_active = Touchpad::Type::NONE; double touchpad_last_angle = 0.0; // in radians, compared to the x axis double touchpad_last_scale = 1.0; // last scale sent in a pinch gesture bool next_release_touchpad = false; // if true, do not propagate the next button release event bool ignore_next_own_btn = false; // allow to generate a click that will be ignored uint32_t touchpad_fingers = 0; // number of fingers in the current touchpad gesture bool ptr_moved = false; wf::wl_timer timeout; std::string config_dir; std::string config_file; /* Handle views being unmapped -- needed to avoid segfault if the "target" views disappear */ wf::signal::connection_t view_unmapped = [=] (wf::view_unmapped_signal *ev) { auto view = ev->view; if(view) { if(target_view == view) target_view = nullptr; if(initial_active_view == view) { needs_refocus = false; /* "lost" the initially active view, avoid refocusing */ needs_refocus2 = false; initial_active_view = nullptr; } if(mouse_view == view) mouse_view = nullptr; } }; /* scenegraph node for drawing an overlay -- it is active * (i.e. added to the scenegraph) iff. is_gesture == true */ std::shared_ptr overlay_node; public: wstroke() { stroke_initiate = [=](const wf::buttonbinding_t& btn) { auto p = output->get_cursor_position(); return start_stroke(p.x, p.y); }; char* xdg_config = getenv("XDG_CONFIG_HOME"); if(xdg_config) config_dir = std::string(xdg_config) + "/wstroke/"; else config_dir = std::string(getenv("HOME")) + "/.config/wstroke/"; config_file = config_dir + ActionDB::wstroke_actions_versions[0]; } ~wstroke() { fini(); } void init() override { inotify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); reload_config(); inotify_source = wl_event_loop_add_fd(wf::get_core().ev_loop, inotify_fd, WL_EVENT_READABLE, config_updated, this); /* start the headless backend, but not instantly since it * might be started automatically by the core multi_backend */ idle_generate.run_once([this] () { input.init(); }); overlay_node = std::make_shared(output); output->add_button(initiate, &stroke_initiate); wf::get_core().connect(&on_raw_pointer_button); wf::get_core().connect(&on_raw_pointer_motion); // wf::get_core().connect_signal("keyboard_key_post", &ignore_key_cb); -- ignore does not work combined with the real keyboard input_grab = std::make_unique(this->grab_interface.name, output, nullptr, this, nullptr); input_grab->set_wants_raw_input(true); } void fini() override { if(active) cancel_stroke(); on_raw_pointer_button.disconnect(); on_raw_pointer_motion.disconnect(); // ignore_key_cb.disconnect(); output->rem_binding(&stroke_initiate); input.fini(); overlay_node = nullptr; actions.reset(); if(inotify_source) { wl_event_source_remove(inotify_source); inotify_source = nullptr; } if(inotify_fd >= 0) { close(inotify_fd); inotify_fd = -1; } } /* pointer tracking interface */ void handle_pointer_button(const wlr_pointer_button_event& event) override { wf::buttonbinding_t tmp = initiate; if(event.button == tmp.get_button() && event.state == WLR_BUTTON_RELEASED) { if(start_timeout > 0 && !ptr_moved) timeout.set_timeout(start_timeout, [this]() { end_stroke(); }); else end_stroke(); } } void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) override { ptr_moved = true; auto geom = output->get_layout_geometry(); handle_input_move(pointer_position.x - geom.x, pointer_position.y - geom.y); } /* visitor interface for carrying out actions */ void visit(const Command* action) override { const auto& cmd = action->get_cmd(); LOGD("Running command: ", cmd); set_idle_action([cmd] () {wf::get_core().run(cmd);}, false); } void visit(const SendKey* action) override { uint32_t mod = action->get_mods(); uint32_t key = action->get_key(); if(key) { set_idle_action([this, mod, key] () { uint32_t t = wf::get_current_time(); keyboard_modifiers(t, mod, WL_KEYBOARD_KEY_STATE_PRESSED); if(mod) input.keyboard_mods(mod, 0, 0); input.keyboard_key(t, key - 8, WL_KEYBOARD_KEY_STATE_PRESSED); t++; input.keyboard_key(t, key - 8, WL_KEYBOARD_KEY_STATE_RELEASED); keyboard_modifiers(t, mod, WL_KEYBOARD_KEY_STATE_RELEASED); if(mod) input.keyboard_mods(0, 0, 0); }); } } void visit(const SendText* action) override { LOGW("SendText action not implemented!"); } void visit(const Scroll* action) override { LOGW("Scroll action not implemented!"); } void visit(const Ignore* action) override { uint32_t ignore_mods = action->get_mods(); set_idle_action([this, ignore_mods] () { uint32_t t = wf::get_current_time(); keyboard_modifiers(t, ignore_mods, WL_KEYBOARD_KEY_STATE_PRESSED); input.keyboard_mods(ignore_mods, 0, 0); ignore_active = ignore_mods; }); } void visit(const Button* action) override { uint32_t btn = action->get_button(); uint32_t mod = action->get_mods(); // convert button to event code (btn is 1-based mouse button number) switch(btn) { case 1: btn = BTN_LEFT; break; case 2: btn = BTN_MIDDLE; break; case 3: btn = BTN_RIGHT; break; default: LOGW("Unsupported mouse button: ", btn); return; } set_idle_action([this, mod, btn] () { uint32_t t = wf::get_current_time(); if(mod) { keyboard_modifiers(t, mod, WL_KEYBOARD_KEY_STATE_PRESSED); input.keyboard_mods(mod, 0, 0); } input.pointer_button(t, btn, WLR_BUTTON_PRESSED); t++; input.pointer_button(t, btn, WLR_BUTTON_RELEASED); if(mod) { keyboard_modifiers(t, mod, WL_KEYBOARD_KEY_STATE_RELEASED); input.keyboard_mods(0, 0, 0); } }); } /* global actions */ void visit(const Global* action) override { std::string plugin_activator; switch(action->get_action_type()) { case Global::Type::EXPO: plugin_activator = "expo/toggle"; break; case Global::Type::SCALE: plugin_activator = "scale/toggle"; break; case Global::Type::SCALE_ALL: plugin_activator = "scale/toggle_all"; break; case Global::Type::SHOW_DESKTOP: plugin_activator = "wm-actions/toggle_showdesktop"; break; case Global::Type::CUBE: plugin_activator = "cube/activate"; break; case Global::Type::SHOW_CONFIG: set_idle_action([] () {wf::get_core().run("wstroke-config");}, false); /* fallthrough */ default: return; } call_plugin(plugin_activator); } /* actions on the currently active view */ void visit(const View* action) override { if(!target_view) return; wayfire_toplevel_view toplevel = wf::toplevel_cast(target_view); switch(action->get_action_type()) { case View::Type::CLOSE: target_view->close(); break; case View::Type::MINIMIZE: if(toplevel) wf::get_core().default_wm->minimize_request(toplevel, true); break; case View::Type::MAXIMIZE: /* toggle maximized state */ if(toplevel) { if(toplevel->pending_tiled_edges() == wf::TILED_EDGES_ALL) wf::get_core().default_wm->tile_request(toplevel, 0); else wf::get_core().default_wm->tile_request(toplevel, wf::TILED_EDGES_ALL); } break; case View::Type::MOVE: if(toplevel) { /* in this case we don't refocus the original view, * since the move plugin will raise the selected view, * so it would be confusing for it not to end up * focused as well */ needs_refocus = false; set_idle_action([this] () { if(target_view) { wayfire_toplevel_view toplevel = wf::toplevel_cast(target_view); if(toplevel) { /* we generate a "fake" click so that the move will * commence from the current pointer location */ ignore_next_own_btn = true; uint32_t t = wf::get_current_time(); input.pointer_button(t, BTN_LEFT, WLR_BUTTON_PRESSED); t++; input.pointer_button(t, BTN_LEFT, WLR_BUTTON_RELEASED); wf::get_core().default_wm->move_request(toplevel); } } }); } break; case View::Type::RESIZE: if(toplevel) wf::get_core().default_wm->resize_request(toplevel, get_resize_edges()); break; case View::Type::FULLSCREEN: if(toplevel) call_plugin("wm-actions/set-fullscreen", true, { {"state", !toplevel->toplevel()->current().fullscreen} }); break; case View::Type::SEND_TO_BACK: call_plugin("wm-actions/send-to-back", true); break; case View::Type::ALWAYS_ON_TOP: call_plugin("wm-actions/set-always-on-top", true, { {"state", !target_view->has_data("wm-actions-above")} }); break; case View::Type::STICKY: if(toplevel) call_plugin("wm-actions/set-sticky", true, { {"state", !toplevel->sticky} }); break; default: break; } } void visit(const Plugin* action) override { call_plugin(action->get_action(), true); } void visit(const Touchpad* action) override { auto type = action->get_action_type(); // needs_refocus = false; uint32_t mods = action->get_mods(); uint32_t fingers = action->fingers; set_idle_action([this, type, mods, fingers] () { if(mods) { uint32_t t = wf::get_current_time(); keyboard_modifiers(t, mods, WL_KEYBOARD_KEY_STATE_PRESSED); input.keyboard_mods(mods, 0, 0); ignore_active = mods; } start_touchpad(type, fingers, wf::get_current_time()); }); } protected: /* set the action taken by the idle callback; * this automatically handles refocusing if needed */ template void set_idle_action(CB&& cb, bool refocus_after_action = true) { needs_refocus2 = needs_refocus; idle_generate.run_once([this, cb, refocus_after_action] () { if(needs_refocus2 && !refocus_after_action) wf::get_core().seat->focus_view(initial_active_view); cb(); if(needs_refocus2 && refocus_after_action) wf::get_core().seat->focus_view(initial_active_view); view_unmapped.disconnect(); }); needs_refocus = false; } /* load / reload the configuration; also set up a watch for changes */ void reload_config() { ActionDB* actions_tmp = new ActionDB(); if(actions_tmp) { bool config_read = false; try { std::error_code ec; if(std::filesystem::exists(config_file, ec) && std::filesystem::is_regular_file(config_file, ec)) config_read = actions_tmp->read(config_file, true); else { std::string config_file_old = config_dir + ActionDB::wstroke_actions_versions[1]; config_read = actions_tmp->read(config_file_old, true); } } catch(std::exception& e) { LOGE(e.what()); } if(!config_read) { LOGW("Could not find configuration file. Run the wstroke-config program first to assign actions to gestures."); delete actions_tmp; } else actions.reset(actions_tmp); } if(inotify_fd >= 0) { inotify_add_watch(inotify_fd, config_dir.c_str(), IN_CREATE | IN_MOVED_TO); inotify_add_watch(inotify_fd, config_file.c_str(), IN_CLOSE_WRITE); } } void handle_config_updated() { while(read(inotify_fd, inotify_buffer, inotify_buffer_size) > 0) { } reload_config(); } static int config_updated(int fd, uint32_t mask, void* ptr) { wstroke* w = (wstroke*)ptr; w->handle_config_updated(); return 0; } /* Determine which corner of a view to start a resize action from. */ uint32_t get_resize_edges() const { const std::string& e = resize_edges; if(e == "auto") return 0; if(e == "top_left") return WLR_EDGE_TOP | WLR_EDGE_LEFT; if(e == "top_right") return WLR_EDGE_TOP | WLR_EDGE_RIGHT; if(e == "bottom_left") return WLR_EDGE_BOTTOM | WLR_EDGE_LEFT; if(e == "bottom_right") return WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT; return 0; // not reached } /* call a plugin activator -- do this from the idle_call, so * that our grab interface does not get in the way; also take * care of refocusing the original view if needed */ void call_plugin(const std::string& plugin_activator, bool include_view = false, nlohmann::json data = nlohmann::json()) { data["output_id"] = output->get_id(); if(include_view) data["view_id"] = target_view->get_id(); set_idle_action([this, plugin_activator, data] () { LOGI("Call plugin: ", plugin_activator); wf::shared_data::ref_ptr_t repo; repo->call_method(plugin_activator, data); }); } /* focus the view under the mouse if needed -- no gesture case */ void check_focus_mouse_view() { if(mouse_view) { const std::string& mode = focus_mode; if(mode == "no_gesture" || mode == "always") wf::get_core().default_wm->focus_raise_view(mouse_view); } } /* callback when the stroke mouse button is pressed */ bool start_stroke(int32_t x, int32_t y) { if(!actions) return false; if(active) { LOGW("already active!"); return false; } /* note: end any previously running stroke action */ end_touchpad(); end_ignore(); initial_active_view = wf::get_core().seat->get_active_view(); if(initial_active_view && initial_active_view->role == wf::VIEW_ROLE_DESKTOP_ENVIRONMENT) initial_active_view = nullptr; mouse_view = wf::get_core().get_cursor_focus_view(); if(mouse_view && mouse_view->role == wf::VIEW_ROLE_DESKTOP_ENVIRONMENT) mouse_view = nullptr; target_view = target_mouse ? mouse_view : initial_active_view; if(target_view) { const std::string& app_id = target_view->get_app_id(); if(actions->exclude_app(app_id)) { LOGD("Excluding strokes for app: ", app_id); if(initial_active_view != mouse_view) check_focus_mouse_view(); return false; } } /* listen to views being unmapped to handle the case when * initial_active_view or mouse_view disappears while running */ output->connect(&view_unmapped); if(!output->activate_plugin(&grab_interface, 0)) { LOGE("could not activate"); view_unmapped.disconnect(); return false; } input_grab->grab_input(wf::scene::layer::OVERLAY); active = true; ps.push_back(Stroke::Point{(double)x, (double)y}); return true; } /* callback when the mouse is moved */ void handle_input_move(int32_t x, int32_t y) { if(ps.size()) { const auto& tmp = ps.back(); /* ignore events without actual movement */ if(x == tmp.x && y == tmp.y) return; } Stroke::Point t{(double)x, (double)y}; if(!is_gesture) { float dist = hypot(t.x - ps.front().x, t.y - ps.front().y); if(dist > 16.0f) { is_gesture = true; start_drawing(); if(target_mouse && target_view && target_view != initial_active_view) { const std::string& mode = focus_mode; if(mode == "always" || mode == "only_gesture") needs_refocus = false; else needs_refocus = true; needs_refocus2 = false; /* set this to false, will be set to true if needed later */ /* raise the view if it should stay focused after the gesture */ if(needs_refocus) wf::get_core().seat->focus_view(target_view); else wf::get_core().default_wm->focus_raise_view(target_view); } } } ps.push_back(t); if(is_gesture) overlay_node->draw_line(ps[ps.size()-2].x, ps[ps.size()-2].y, ps.back().x, ps.back().y); if(timeout.is_connected()) { timeout.disconnect(); int timeout_len = end_timeout > 0 ? end_timeout : start_timeout; timeout.set_timeout(timeout_len, [this]() { end_stroke(); }); } } /* start drawing the stroke on the screen */ void start_drawing() { wf::scene::add_front(output->node_for_layer(wf::scene::layer::OVERLAY), overlay_node); for(size_t i = 1; i < ps.size(); i++) overlay_node->draw_line(ps[i-1].x, ps[i-1].y, ps[i].x, ps[i].y); } /* callback when the mouse button is released */ void end_stroke() { if(!active) return; /* in case the timeout was not disconnected */ timeout.disconnect(); ptr_moved = false; input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); if(is_gesture) { overlay_node->clear_lines(); wf::scene::remove_child(overlay_node); Stroke stroke(ps); /* try to match the stroke, write out match */ const ActionListDiff* matcher = nullptr; if(target_view) { const std::string& app_id = target_view->get_app_id(); LOGD("Target app id: ", app_id); matcher = actions->get_action_list(app_id); } if(!matcher) matcher = actions->get_root(); Ranking rr; Action* action = matcher->handle(stroke, &rr); if(action) { LOGD("Matched stroke: ", rr.name); action->visit(this); } else LOGD("Unmatched stroke"); if(needs_refocus) /* set an "empty" action that will ensure that the original * focused view is refocused (if possible) */ set_idle_action([](){}); else if(!needs_refocus2) view_unmapped.disconnect(); is_gesture = false; } else { /* Generate a "fake" mouse click to pass on to the view * originally under the mouse. * * Note: we cannot directly generate a click since using the * grab interface "unfocuses" any view under the cursor for * the purpose of receiving these events. The * grab_interface->ungrab() call does not instantly reset * this to avoid propagating this event, but adds the * necessary "refocus" to the idle loop. With this call, * we are adding the emulated click to the idle loop as well. */ idle_generate.run_once([this]() { check_focus_mouse_view(); const wf::buttonbinding_t& tmp = initiate; auto t = wf::get_current_time(); output->rem_binding(&stroke_initiate); input.pointer_button(t, tmp.get_button(), WLR_BUTTON_PRESSED); input.pointer_button(t, tmp.get_button(), WLR_BUTTON_RELEASED); output->add_button(initiate, &stroke_initiate); view_unmapped.disconnect(); }); } ps.clear(); active = false; } /* helpers for the ignore action */ void end_ignore() { if(ignore_active) { uint32_t t = wf::get_current_time(); keyboard_modifiers(t, ignore_active, WL_KEYBOARD_KEY_STATE_RELEASED); input.keyboard_mods(0, 0, 0); ignore_active = 0; } } /* wf::signal_connection_t ignore_key_cb{[this] (wf::signal_data_t *data) { auto k = static_cast*>(data); if(k->event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { // ignore modifiers possibly generated by us switch(k->event->keycode) { case KEY_LEFTSHIFT: case KEY_RIGHTSHIFT: case KEY_LEFTCTRL: case KEY_RIGHTCTRL: case KEY_LEFTALT: case KEY_RIGHTALT: case KEY_LEFTMETA: case KEY_RIGHTMETA: return; default: end_ignore(); } } }}; */ void start_touchpad(Touchpad::Type type, uint32_t fingers, uint32_t time_msec) { touchpad_fingers = fingers; switch(type) { case Touchpad::Type::SWIPE: input.pointer_start_swipe(time_msec, touchpad_fingers); break; case Touchpad::Type::PINCH: input.pointer_start_pinch(time_msec, touchpad_fingers); touchpad_last_angle = -1.0 * M_PI / 2.0; touchpad_last_scale = 1.0; break; case Touchpad::Type::NONE: case Touchpad::Type::SCROLL: /* Note: no action needed for SCROLL */ break; } touchpad_active = type; } void end_touchpad(bool cancelled = false) { switch(touchpad_active) { case Touchpad::Type::SWIPE: input.pointer_end_swipe(wf::get_current_time(), cancelled); break; case Touchpad::Type::PINCH: input.pointer_end_pinch(wf::get_current_time(), cancelled); break; case Touchpad::Type::NONE: case Touchpad::Type::SCROLL: /* Note: no action needed for SCROLL */ break; } touchpad_active = Touchpad::Type::NONE; } wf::signal::connection_t> on_raw_pointer_button = [=] (wf::input_event_signal *ev) { if(ev->event->state == WLR_BUTTON_PRESSED) { if(touchpad_active != Touchpad::Type::NONE) { next_release_touchpad = true; ev->mode = wf::input_event_processing_mode_t::IGNORE; } else if(ignore_next_own_btn && input.is_own_event_btn(ev->event)) ev->mode = wf::input_event_processing_mode_t::IGNORE; } if(ev->event->state == WLR_BUTTON_RELEASED) { if(next_release_touchpad) { ev->mode = wf::input_event_processing_mode_t::IGNORE; next_release_touchpad = false; } else if(ignore_next_own_btn && input.is_own_event_btn(ev->event)) { ev->mode = wf::input_event_processing_mode_t::IGNORE; ignore_next_own_btn = false; } end_touchpad(); end_ignore(); } }; wf::signal::connection_t> on_raw_pointer_motion = [=] (wf::input_event_signal *ev) { switch(touchpad_active) { case Touchpad::Type::NONE: return; case Touchpad::Type::SCROLL: { LOGD("Scroll event, dx: ", ev->event->delta_x, ", dy: ", ev->event->delta_y); double delta; enum WSTROKE_AXIS_ORIENTATION o; if(std::abs(ev->event->delta_x) > std::abs(ev->event->delta_y)) { delta = ev->event->delta_x; o = WSTROKE_AXIS_HORIZONTAL; } else { delta = ev->event->delta_y; o = WSTROKE_AXIS_VERTICAL; } input.pointer_scroll(ev->event->time_msec + 1, 0.2 * delta * touchpad_scroll_sensitivity, o); } break; case Touchpad::Type::SWIPE: input.pointer_update_swipe(ev->event->time_msec + 1, touchpad_fingers, ev->event->delta_x, ev->event->delta_y); break; case Touchpad::Type::PINCH: { int tmp = touchpad_pinch_sensitivity; double sensitivity = tmp > 0 ? tmp : 200.0; /* TODO: process angles in a reliable way (so far, it does not work, so we just do zoom based on y-coordinates) wf::pointf_t last_pos = {sensitivity * std::cos(touchpad_last_angle), sensitivity * std::sin(touchpad_last_angle)}; wf::pointf_t new_pos = last_pos; new_pos.x += ev->event->delta_x; new_pos.y += ev->event->delta_y; double new_angle = std::atan2(new_pos.y, new_pos.x); double angle_diff = touchpad_last_angle - new_angle; if(new_pos.x < 0.0 && last_pos.x < 0.0) { if(new_angle < 0.0 && touchpad_last_angle > 0.0) angle_diff -= 2.0 * M_PI; else if(new_angle > 0.0 && touchpad_last_angle < 0.0) angle_diff += 2.0 * M_PI; } double delta_angle_diff = std::abs(last_pos.x * ev->event->delta_x + last_pos.y * ev->event->delta_y) / (sensitivity * std::hypot(ev->event->delta_x, ev->event->delta_y)); if(delta_angle_diff < 0.5) angle_diff = 0.0; else touchpad_last_angle = new_angle; double scale_factor = (delta_angle_diff > 0.5) ? std::hypot(new_pos.x, new_pos.y) / sensitivity : 1.0; touchpad_last_scale *= scale_factor; input.pointer_update_pinch(time_msec, 2, 0.0, 0.0, touchpad_last_scale, -180.0 * angle_diff / M_PI); */ double scale_factor = (sensitivity - ev->event->delta_y) / sensitivity; if(scale_factor > 0.0) { touchpad_last_scale *= scale_factor; uint32_t time_msec = ev->event->time_msec + 1; input.pointer_update_pinch(time_msec, touchpad_fingers, 0.0, 0.0, touchpad_last_scale, 0.0); } } break; } ev->mode = wf::input_event_processing_mode_t::IGNORE; }; /* callback to cancel a stroke */ void cancel_stroke() { input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); end_touchpad(true); end_ignore(); ps.clear(); if(is_gesture) { overlay_node->clear_lines(); wf::scene::remove_child(overlay_node); is_gesture = false; } if(target_mouse) wf::get_core().seat->focus_view(initial_active_view); active = false; ptr_moved = false; timeout.disconnect(); view_unmapped.disconnect(); } static constexpr std::array, 4> mod_map = { std::pair(WLR_MODIFIER_SHIFT, KEY_LEFTSHIFT), std::pair(WLR_MODIFIER_CTRL, KEY_LEFTCTRL), std::pair(WLR_MODIFIER_ALT, KEY_LEFTALT), std::pair(WLR_MODIFIER_LOGO, KEY_LEFTMETA) }; void keyboard_modifiers(uint32_t t, uint32_t mod, enum wl_keyboard_key_state state) { for(const auto& x : mod_map) if(x.first & mod) input.keyboard_key(t, x.second, state); } }; constexpr std::array, 4> wstroke::mod_map; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t) wstroke-2.2.1/src/gesture.cc000066400000000000000000000040061472137506600160010ustar00rootroot00000000000000/* * Copyright (c) 2008-2009, Thomas Jaeger * Copyright (c) 2020-2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "gesture.h" #include #include #include #include #include #include BOOST_CLASS_EXPORT(Stroke) Stroke::Stroke(const PreStroke &ps) : stroke(nullptr, stroke_deleter()) { if (ps.size() >= 2) { stroke_t *s = stroke_alloc(ps.size()); for (const auto& t : ps) stroke_add_point(s, t.x, t.y); stroke_finish(s); stroke.reset(s); } } int Stroke::compare(const Stroke& a, const Stroke& b, double &score) { score = 0.0; if (!a.stroke || !b.stroke) { if (!a.stroke && !b.stroke) { score = 1.0; return 1; } return -1; } double cost = stroke_compare(a.stroke.get(), b.stroke.get(), nullptr, nullptr); if (cost >= stroke_infinity) return -1; score = std::max(1.0 - 2.5*cost, 0.0); return score > 0.7; } /* Stroke Stroke::trefoil() { PreStroke s; const unsigned int n = 40; s.reserve(n); for (unsigned int i = 0; i<=n; i++) { double phi = M_PI*(-4.0*i/n)-2.7; double r = exp(1.0 + sin(6.0*M_PI*i/n)) + 2.0; s.add(Point{(float)(r*cos(phi)), (float)(r*sin(phi))}); } return Stroke(s); } */ wstroke-2.2.1/src/gesture.h000066400000000000000000000072441472137506600156520ustar00rootroot00000000000000/* * Copyright (c) 2008-2009, Thomas Jaeger * Copyright (c) 2020-2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __GESTURE_H__ #define __GESTURE_H__ #include "stroke.h" #include #include #include #include #include #include #include #define STROKE_SIZE 64 class Stroke { friend class boost::serialization::access; public: struct Point { double x; double y; Point operator+(const Point &p) { Point sum = { x + p.x, y + p.y }; return sum; } Point operator-(const Point &p) { Point sum = { x - p.x, y - p.y }; return sum; } Point operator*(const double a) { Point product = { x * a, y * a }; return product; } template void serialize(Archive & ar, const unsigned int version) { ar & x; ar & y; if (version == 0) { double time; ar & time; } } }; using PreStroke = std::vector; private: BOOST_SERIALIZATION_SPLIT_MEMBER() template void load(Archive & ar, const unsigned int version) { if(version >= 6) { unsigned int n; ar & n; if(n) { stroke_t* s = stroke_alloc(n); for(unsigned int i = 0; i < n; i++) { double x, y; ar & x; ar & y; stroke_add_point(s, x, y); } stroke_finish(s); stroke.reset(s); } return; } std::vector ps; ar & ps; if (ps.size()) { stroke_t *s = stroke_alloc(ps.size()); for (std::vector::iterator i = ps.begin(); i != ps.end(); ++i) stroke_add_point(s, i->x, i->y); stroke_finish(s); stroke.reset(s); } if (version == 0) return; int trigger; int button; unsigned int modifiers; bool timeout; ar & button; if (version >= 2) ar & trigger; if (version < 3) return; ar & timeout; if (version < 5) return; ar & modifiers; } template void save(Archive & ar, __attribute__((unused)) unsigned int version) const { unsigned int n = size(); ar & n; for(unsigned int i = 0; i < n; i++) { Point p = points(i); ar & p.x; ar & p.y; } } struct stroke_deleter { void operator()(stroke_t* s) const { stroke_free(s); } }; public: std::unique_ptr stroke; Stroke() : stroke(nullptr, stroke_deleter()) { } Stroke(const PreStroke &s); Stroke clone() const { Stroke s; if(stroke) s.stroke.reset(stroke_copy(stroke.get())); return s; } static Stroke trefoil(); static int compare(const Stroke&, const Stroke&, double &); unsigned int size() const { return stroke ? stroke_get_size(stroke.get()) : 0; } bool trivial() const { return size() == 0 ; } Point points(int n) const { Point p; stroke_get_point(stroke.get(), n, &p.x, &p.y); return p; } double time(int n) const { return stroke_get_time(stroke.get(), n); } }; BOOST_CLASS_VERSION(Stroke, 6) BOOST_CLASS_VERSION(Stroke::Point, 1) #endif wstroke-2.2.1/src/gui.glade000066400000000000000000002322001472137506600155750ustar00rootroot00000000000000 True False media-record True False list-add True False gtk-clear True False document-open True False document-save-as True False document-open True False list-add True False list-remove True False list-add True False gtk-dialog-warning True False gtk-delete True False list-remove True False list-add True False list-remove False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK center 800 600 wstroke True False slide-left-right True True vertical True True False 5 True True 80 True True True True 0 True False vertical 4 start Add Application False True True True image8 True False False 0 Add Group False True True True image10 True False False 1 Remove Application/Group False True True True image9 True False True 4 end 2 False True 4 end 1 False True True False vertical 5 True False True False Gestures False True 0 Show deleted rows False True True False 0 True False True end 1 False True 0 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 600 300 True True 1 True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 12 start _Record Stroke False True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK image1 True True False False 0 _Add Action False True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK image2 True True False False 1 _Delete Action(s) False True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK image3 True True False False 2 Reset Action(s) False True True True image11 True True True end 3 False True 4 2 True False page2 Actions True False vertical True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 none True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 12 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Exceptions</b>: gestures are disabled for these apps True True True 0 True False start _Add Exception False True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK image4 True True False False 0 _Remove Exception False True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK image7 True True False False 1 False True 1 page1 Exceptions 1 True False True True False center center main_stack gtk-about False True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True Export True True True image13 True end 1 Import True True True image12 True end 2 True dialog main WStroke 2.0 Copyright (c) 2020-2023, Daniel Kondor <kondor.dani@gmail.com> Copyright (c) 2008-2016, Thomas Jaeger <ThJaeger@gmail.com> Mouse gesture recognition plugin for Wayfire based on Easystroke. https://github.com/dkondor/wstroke https://github.com/dkondor/wstroke Copyright (c) 2008-2009, Thomas Jaeger <ThJaeger@gmail.com> Copyright (c) 2020-2023, Daniel Kondor <kondor.dani@gmail.com> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. wstroke custom False vertical 2 False end False False 0 False True 450 400 dialog main False vertical 2 False end gtk-cancel False True True True True True False False 0 gtk-ok False True True True True True True True True False False 1 False False 0 True False vertical True True edit-find-symbolic False False False True 0 True True never in 400 300 False True 1 Use a custom command True True False True False True 2 True True True False True 3 False True 1 appchooser_cancel appchooser_ok True False App selection True appchooser_cancel appchooser_ok False 5 normal True main warning cancel True False 2 True False end gtk-delete False True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True False False 1 False True end 0 button_delete_delete False 5 normal True main ok True False 2 True False end False True end 0 False 5 normal True main True False 2 True False end gtk-cancel False True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True False False 0 _Delete Current False True True True image6 True True False False 1 False True end 0 button_record_cancel button_record_delete False 5 normal True main True False vertical 6 True False end gtk-cancel False True True True True True False False 0 gtk-ok False True True True True True True True False False 1 False True end 0 True False 2 3 6 6 True _Control False True True True True 1 2 _Shift False True True True True _Alt False True True True True 1 2 S_uper False True True True True 1 2 1 2 True False 2 3 1 2 False False end 2 True False Alternatively, you may select button and modifiers below. True True True end 3 200 True False GDK_BUTTON_PRESS_MASK True True end 4 select_cancel select_ok False False True True dialog True True False main False vertical 2 True False start Import from file False True 2 0 True False False Choose gesture action database False True 3 1 Replace the current set of gestures True True False True True False True 2 Add to the existing gestures True True False True import_overwrite False True 3 True False False True 30 4 Find Easystroke configuration True True True none False True 5 Use the default configuration True True True none False True 6 True False True False False 6 end True False True 40 40 True True 0 False False 0 False 16 False False 0 False True 7 True False Import gestures False gtk-cancel True True True True True Import True True True True True image14 True end 1 False 600 wstroke dialog False vertical 2 False end Overwrite True True True image5 True True True 0 gtk-quit True True True True True True True 1 False False 0 True False vertical 10 True False True False True 0 True True False False textbuffer_config_error False True True True 1 True False This can be caused by the configuration file created by an incompatible version of WStroke, or the file being damaged. You can quit to resolve this manually, or choose to overwrite the configuration file (a backup of the current file will be saved in the configuration directory). True False True 2 True True 1 button_overwrite button_quit True False WStroke config Error reading gestures button_overwrite button_quit 2 20 2 1 1 False False True dialog main False vertical 10 False end gtk-cancel False True True True True True False False 0 gtk-ok False True True True True True True True True True False False 1 False False 3 True False 20 True False Action type: False True 0 Scroll True True False True True False True 1 Pinch True True False True touchpad_type_scroll False True 2 Swipe True True False True touchpad_type_scroll False True 3 False True 0 True False True False Fingers: False True 0 True True True 3 number touchpad_fingers_adj 1 True if-valid 2 False True end 1 False True 1 True False Modifiers: False True 2 True False 4 start _Control False True True True True True True 0 _Shift False True True True True True True 1 S_uper False True True True True True True 2 _Alt False True True True True True True 3 False True 4 touchpad_select_cancel touchpad_select_ok True False Touchpad action True touchpad_select_cancel touchpad_select_ok wstroke-2.2.1/src/input_events.cpp000066400000000000000000000201241472137506600172420ustar00rootroot00000000000000/* * input_events.cpp -- interface to generate input events in Wayfire * * Copyright 2020-2024 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "input_events.hpp" #include extern "C" { #define static #include #include #include #include #include #include #undef static } #include #include static const struct wlr_pointer_impl ws_headless_pointer_impl = { .name = "wstroke-pointer", }; static const struct wlr_keyboard_impl ws_headless_keyboard_impl = { .name = "wstroke-keyboard", .led_update = nullptr }; void input_headless::init() { auto& core = wf::compositor_core_t::get(); /* 1. create headless backend */ #ifdef WSTROKE_WLR_VERSION_018 headless_backend = wlr_headless_backend_create(core.ev_loop); #else headless_backend = wlr_headless_backend_create(core.display); #endif if(!headless_backend) { LOGE("Cannot create headless wlroots backend!"); return; } /* 2. add to the core backend */ if(!wlr_multi_backend_add(core.backend, headless_backend)) { LOGE("Cannot add headless wlroots backend!"); wlr_backend_destroy(headless_backend); headless_backend = nullptr; return; } /* 3. start the new headless backend */ start_backend(); /* 4. create the new input device */ input_pointer = (struct wlr_pointer*)calloc(1, sizeof(struct wlr_pointer)); if(!input_pointer) { LOGE("Cannot create pointer device!"); fini(); return; } wlr_pointer_init(input_pointer, &ws_headless_pointer_impl, ws_headless_pointer_impl.name); input_keyboard = (struct wlr_keyboard*)calloc(1, sizeof(struct wlr_keyboard)); if(!input_keyboard) { LOGE("Cannot create keyboard device!"); fini(); return; } wlr_keyboard_init(input_keyboard, &ws_headless_keyboard_impl, ws_headless_keyboard_impl.name); wl_signal_emit_mutable(&headless_backend->events.new_input, input_keyboard); wl_signal_emit_mutable(&headless_backend->events.new_input, input_pointer); } void input_headless::start_backend() { if(!wlr_backend_start(headless_backend)) { LOGE("Cannot start headless wlroots backend!"); fini(); } } void input_headless::fini() { if(input_pointer) { wlr_pointer_finish(input_pointer); free(input_pointer); input_pointer = nullptr; } if(input_keyboard) { wlr_keyboard_finish(input_keyboard); free(input_keyboard); input_keyboard = nullptr; } if(headless_backend) { auto& core = wf::compositor_core_t::get(); wlr_multi_backend_remove(core.backend, headless_backend); wlr_backend_destroy(headless_backend); headless_backend = nullptr; } } void input_headless::pointer_button(uint32_t time_msec, uint32_t button, enum WSTROKE_BUTTON_STATE state) { if(!(input_pointer && headless_backend)) { LOGW("No input device created!"); return; } LOGD("Emitting pointer button event"); wlr_pointer_button_event ev; ev.pointer = input_pointer; ev.button = button; ev.state = state; ev.time_msec = time_msec; wl_signal_emit(&(input_pointer->events.button), &ev); } void input_headless::pointer_scroll(uint32_t time_msec, double delta, enum WSTROKE_AXIS_ORIENTATION o) { if(!(input_pointer && headless_backend)) { LOGW("No input device created!"); return; } LOGD("Emitting pointer scroll event"); wlr_pointer_axis_event ev; ev.pointer = input_pointer; ev.time_msec = time_msec; ev.source = WLR_AXIS_SOURCE_CONTINUOUS; ev.orientation = o; ev.delta = delta; ev.delta_discrete = delta * WLR_POINTER_AXIS_DISCRETE_STEP;; wl_signal_emit(&(input_pointer->events.axis), &ev); } void input_headless::pointer_start_swipe(uint32_t time_msec, uint32_t fingers) { if(!(input_pointer && headless_backend)) { LOGW("No input device created!"); return; } LOGD("Emitting pointer swipe begin event"); wlr_pointer_swipe_begin_event ev; ev.pointer = input_pointer; ev.time_msec = time_msec; ev.fingers = fingers; wl_signal_emit(&(input_pointer->events.swipe_begin), &ev); } void input_headless::pointer_update_swipe(uint32_t time_msec, uint32_t fingers, double dx, double dy) { if(!(input_pointer && headless_backend)) { LOGW("No input device created!"); return; } LOGD("Emitting pointer swipe update event"); wlr_pointer_swipe_update_event ev; ev.pointer = input_pointer; ev.time_msec = time_msec; ev.fingers = fingers; ev.dx = dx; ev.dy = dy; wl_signal_emit(&(input_pointer->events.swipe_update), &ev); } void input_headless::pointer_end_swipe(uint32_t time_msec, bool cancelled) { if(!(input_pointer && headless_backend)) { LOGW("No input device created!"); return; } LOGD("Emitting pointer swipe end event"); wlr_pointer_swipe_end_event ev; ev.pointer = input_pointer; ev.time_msec = time_msec; ev.cancelled = cancelled; //!! note: conversion from C++ bool to C99/C23 bool !! wl_signal_emit(&(input_pointer->events.swipe_end), &ev); } void input_headless::pointer_start_pinch(uint32_t time_msec, uint32_t fingers) { if(!(input_pointer && headless_backend)) { LOGW("No input device created!"); return; } LOGD("Emitting pointer pinch begin event"); wlr_pointer_pinch_begin_event ev; ev.pointer = input_pointer; ev.time_msec = time_msec; ev.fingers = fingers; wl_signal_emit(&(input_pointer->events.pinch_begin), &ev); } void input_headless::pointer_update_pinch(uint32_t time_msec, uint32_t fingers, double dx, double dy, double scale, double rotation) { if(!(input_pointer && headless_backend)) { LOGW("No input device created!"); return; } LOGD("Emitting pointer pinch update event"); wlr_pointer_pinch_update_event ev; ev.pointer = input_pointer; ev.time_msec = time_msec; ev.fingers = fingers; ev.dx = dx; ev.dy = dy; ev.scale = scale; ev.rotation = rotation; wl_signal_emit(&(input_pointer->events.pinch_update), &ev); } void input_headless::pointer_end_pinch(uint32_t time_msec, bool cancelled) { if(!(input_pointer && headless_backend)) { LOGW("No input device created!"); return; } LOGD("Emitting pointer pinch end event"); wlr_pointer_pinch_end_event ev; ev.pointer = input_pointer; ev.time_msec = time_msec; ev.cancelled = cancelled; //!! note: conversion from C++ bool to C99/C23 bool !! wl_signal_emit(&(input_pointer->events.pinch_end), &ev); } void input_headless::keyboard_key(uint32_t time_msec, uint32_t key, enum wl_keyboard_key_state state) { if(!(input_keyboard && headless_backend)) { LOGW("No input device created!"); return; } LOGD("Emitting keyboard event ", key, state == WL_KEYBOARD_KEY_STATE_PRESSED ? ", pressed" : ", released"); wlr_keyboard_key_event ev; ev.keycode = key; ev.state = (decltype(ev.state))state; ev.update_state = true; ev.time_msec = time_msec; wl_signal_emit(&(input_keyboard->events.key), &ev); } void input_headless::keyboard_mods(uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked) { if(!(input_keyboard && headless_backend)) { LOGW("No input device created!"); return; } LOGD("Changing keyboard modifiers"); wlr_keyboard_notify_modifiers(input_keyboard, mods_depressed, mods_latched, mods_locked, 0); /* struct wlr_seat* seat = wf::get_core().get_current_seat(); -- does not work: combining with the "real" keyboard struct wlr_keyboard_modifiers modifiers; modifiers.depressed = mods_depressed; modifiers.latched = mods_latched; modifiers.locked = mods_locked; modifiers.group = 0; // ?? wlr_seat_keyboard_notify_modifiers(seat, &modifiers); */ } wstroke-2.2.1/src/input_events.hpp000066400000000000000000000071351472137506600172560ustar00rootroot00000000000000/* * input_events.hpp -- interface to generate input events in Wayfire * * Copyright 2020-2024 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #ifndef INPUT_EVENTS_HPP #define INPUT_EVENTS_HPP extern "C" { #include #include #include #include #include } #if (WLR_VERSION_NUM >= 4608) #define WLR_BUTTON_RELEASED WL_POINTER_BUTTON_STATE_RELEASED #define WLR_BUTTON_PRESSED WL_POINTER_BUTTON_STATE_PRESSED #define WSTROKE_AXIS_HORIZONTAL wl_pointer_axis::WL_POINTER_AXIS_HORIZONTAL_SCROLL #define WSTROKE_AXIS_VERTICAL wl_pointer_axis::WL_POINTER_AXIS_VERTICAL_SCROLL #define WLR_AXIS_SOURCE_CONTINUOUS WL_POINTER_AXIS_SOURCE_CONTINUOUS #define WSTROKE_BUTTON_STATE wl_pointer_button_state #define WSTROKE_AXIS_ORIENTATION wl_pointer_axis #define WSTROKE_WLR_VERSION_018 #else #define WSTROKE_AXIS_HORIZONTAL wlr_axis_orientation::WLR_AXIS_ORIENTATION_HORIZONTAL #define WSTROKE_AXIS_VERTICAL wlr_axis_orientation::WLR_AXIS_ORIENTATION_VERTICAL #define WSTROKE_BUTTON_STATE wlr_button_state #define WSTROKE_AXIS_ORIENTATION wlr_axis_orientation #endif class input_headless { public: /* init internals, create headless wlroots backend with fake * pointer and add it to the backends managed by Wayfire */ void init(); /* remove the headless backend created by this class and * delete it */ void fini(); /* emit a mouse button event */ void pointer_button(uint32_t time_msec, uint32_t button, enum WSTROKE_BUTTON_STATE state); /* emit a pointer scroll event */ void pointer_scroll(uint32_t time_msec, double delta, enum WSTROKE_AXIS_ORIENTATION o); /* emit a sequence of swipe events */ void pointer_start_swipe(uint32_t time_msec, uint32_t fingers); void pointer_update_swipe(uint32_t time_msec, uint32_t fingers, double dx, double dy); void pointer_end_swipe(uint32_t time_msec, bool cancelled); /* emit a sequence of pinch events */ void pointer_start_pinch(uint32_t time_msec, uint32_t fingers); void pointer_update_pinch(uint32_t time_msec, uint32_t fingers, double dx, double dy, double scale, double rotation); void pointer_end_pinch(uint32_t time_msec, bool cancelled); /* emit a keyboard event */ void keyboard_key(uint32_t time_msec, uint32_t key, enum wl_keyboard_key_state state); /* modify the modifier state of the keyboard */ void keyboard_mods(uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked); /* return if a pointer event was generated by us */ bool is_own_event_btn(const wlr_pointer_button_event* ev) const { return ev && (ev->pointer == input_pointer); } ~input_headless() { fini(); } protected: void start_backend(); struct wlr_backend* headless_backend = nullptr; struct wlr_pointer* input_pointer = nullptr; struct wlr_keyboard* input_keyboard = nullptr; }; #endif wstroke-2.2.1/src/input_inhibitor.vapi000066400000000000000000000002761472137506600201100ustar00rootroot00000000000000 [CCode (cheader_filename = "input_inhibitor.h")] namespace Inhibitor { [CCode (cname = "input_inhibitor_grab")] bool grab(); [CCode (cname = "input_inhibitor_ungrab")] void ungrab(); } wstroke-2.2.1/src/main.cc000066400000000000000000000157761472137506600152670ustar00rootroot00000000000000/* * main.cc * * Copyright 2020-2023 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include #include #include #include #include #include #include #include #include "actions.h" #include "actiondb.h" #include "ecres.h" #include "convert_keycodes.h" #include "input_inhibitor.h" #include "config.h" static void error_dialog(const Glib::ustring &text) { Gtk::MessageDialog dialog(text, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dialog.show(); dialog.run(); } /* Display a dialog with an error text if the configuration cannot be read. */ bool config_error_dialog(const Glib::ustring& fn, const Glib::ustring& err, Gtk::Builder* widgets) { std::unique_ptr dialog; { Gtk::Dialog* tmp; widgets->get_widget("dialog_config_error", tmp); dialog.reset(tmp); } Glib::ustring msg = Glib::ustring::compose(_("The gesture configuration file \"%1\" exists but cannot be read. The following error was encountered:"), fn); Gtk::Label* label; Gtk::TextView* tv; widgets->get_widget("label_config_error", label); widgets->get_widget("textview_config_error", tv); tv->get_buffer()->set_text(err); label->set_text(msg); dialog->show(); return (dialog->run() == 1); // note: response == 1 means that user clicked on the "Overwrite" button } void startup(Gtk::Application* app, Actions** p_actions) { char* xdg_config = getenv("XDG_CONFIG_HOME"); std::string home_dir = getenv("HOME"); std::string old_config_dir = home_dir + "/.easystroke/"; std::string config_dir = xdg_config ? std::string(xdg_config) + "/wstroke/" : home_dir + "/.config/wstroke/"; // ensure that config dir exists std::error_code ec; if(std::filesystem::exists(config_dir, ec)) { if(!std::filesystem::is_directory(config_dir, ec)) { error_dialog(Glib::ustring::compose(_( "Path for config files (%1) is not a directory! " "Cannot store configuration. " "You can change the configuration directory " "using the XDG_CONFIG_HOME environment variable." ), config_dir)); return; // note: not creating a main window will result in automatically exiting } } else { if(!std::filesystem::create_directories(config_dir, ec)) { error_dialog(Glib::ustring::compose(_( "Cannot create configuration directory \"%1\"! " "Cannot store the configuration. " "You can change the configuration directory " "using the XDG_CONFIG_HOME environment variable." ), config_dir)); return; // note: not creating a main window will result in automatically exiting } } Glib::RefPtr widgets = Gtk::Builder::create_from_resource("/easystroke/gui.glade"); auto actions = new Actions(config_dir, widgets); *p_actions = actions; ActionDB& actions_db = actions->actions; KeyCodes::init(); bool config_read; std::string config_err_msg; std::string easystroke_convert_msg; std::string keycode_err_msg; for(const char* const * x = ActionDB::wstroke_actions_versions; *x; ++x) { std::string fn = config_dir + *x; try { config_read = actions_db.read(fn); } catch(std::exception& e) { fprintf(stderr, "%s\n", e.what()); config_read = false; actions_db.clear(); if(x == ActionDB::wstroke_actions_versions) { /* In this case, the error is with reading the current config * (which would be overwritten by us). Signal an error to the user */ if(!config_error_dialog(fn, e.what(), widgets.get())) return; /* move the configuration file -- try to assign a new filename * in a naive way (we assume that there is no gain from TOCTOU * attacks here :) */ std::string new_fn = fn; new_fn += ".bak"; if(std::filesystem::exists(new_fn, ec)) { std::default_random_engine rng(time(0)); std::uniform_int_distribution dd(1, 999999); new_fn += "-"; while(true) { std::string tmp; tmp = new_fn + std::to_string(dd(rng)); if(!std::filesystem::exists(tmp, ec)) { new_fn = tmp; break; } } } rename(fn.c_str(), new_fn.c_str()); fprintf(stderr, "Moved unreadable config file to new location: %s\n", new_fn.c_str()); config_err_msg = "Created a backup of the previous, unreadable config file here:\n" + new_fn; } } if(config_read) break; } if(!config_read) { if(std::filesystem::exists(old_config_dir, ec) && std::filesystem::is_directory(old_config_dir, ec)) { KeyCodes::keycode_errors = 0; for(const char* const * x = ActionDB::easystroke_actions_versions; *x; ++x) { std::string fn = old_config_dir + *x; try { config_read = actions_db.read(fn); } catch(std::exception& e) { fprintf(stderr, "%s\n", e.what()); config_read = false; actions_db.clear(); } if(config_read) { easystroke_convert_msg = "Imported gestures from Easystroke's configuration:\n" + fn; easystroke_convert_msg += "\nPlease check that all actions were interpreted correctly."; break; } } } if(!config_read) { try { config_read = actions_db.read(std::string(DATA_DIR) + "/" + ActionDB::wstroke_actions_versions[0]); } catch(std::exception& e) { fprintf(stderr, "%s\n", e.what()); } } } if(KeyCodes::keycode_errors) keycode_err_msg = _("Could not convert some keycodes. " "Some Key actions have missing values"); Gtk::Dialog* d = nullptr; if(!(keycode_err_msg.empty() && easystroke_convert_msg.empty() && config_err_msg.empty())) { std::string text; if(!config_err_msg.empty()) text += (config_err_msg + "\n\n"); if(!easystroke_convert_msg.empty()) text += (easystroke_convert_msg + "\n\n"); if(!keycode_err_msg.empty()) text += (keycode_err_msg + "\n\n"); d = new Gtk::MessageDialog(text, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); } if(!input_inhibitor_init()) fprintf(stderr, _("Could not initialize keyboard grabber interface. Assigning key combinations might not work.\n")); actions->startup(app, d); } int main(int argc, char **argv) { Actions* actions = nullptr; auto app = Gtk::Application::create(argc, argv, "org.wstroke.config"); app->signal_startup().connect([&app, &actions]() { startup(app.get(), &actions); }); app->signal_activate().connect([&actions]() { if(actions) actions->get_main_win()->present(); }); int ret = app->run(); if(actions) { actions->exit(); delete actions; } return ret; } wstroke-2.2.1/src/meson.build000066400000000000000000000031101472137506600161510ustar00rootroot00000000000000# resources (GUI layout) econf_res = gnome.compile_resources( 'ecres', 'resources.xml', source_dir: 'data', c_name: 'econf' ) conf_data = configuration_data() conf_data.set('DATA_DIR', join_paths(datadir, 'wstroke')) configure_file(input: 'config.h.in', output: 'config.h', install: false, configuration: conf_data) # note: this is code generated by Vala, compile it separately to # silence warnings cellib = static_library('cellib', 'cellrenderertextish.vala', vala_header: 'cellrenderertextish.h', dependencies: [glib, gobject, gtk, input_inhibitor_dep]) wconf_sources = ['main.cc', 'actiondb_config.cc', 'actiondb.cc', 'actions.cc', 'appchooser.cc', 'gesture.cc', 'stroke_draw.cc', 'stroke.c', 'convert_keycodes.cc', 'stroke_drawing_area.cpp', econf_res] wconf = executable('wstroke-config', wconf_sources, dependencies: [gtkmm, gdkmm, wlroots_headers, boost, protos, input_inhibitor_dep, toplevel_grabber_dep], install: true, cpp_args: ['-DACTIONDB_CONVERT_CODES', '-DWLR_USE_UNSTABLE'], link_with: cellib) wslib_sources = ['easystroke_gestures.cpp', 'input_events.cpp', 'actiondb.cc', 'actiondb_plugin.cc', 'gesture.cc', 'stroke.c'] wslib = shared_module('wstroke', wslib_sources, dependencies: [wayfire, wlroots, wlserver, boost, glibmm, json], install: true, install_dir: wayfire.get_variable(pkgconfig: 'plugindir'), cpp_args: ['-Wno-unused-parameter', '-Wno-format-security','-DWAYFIRE_PLUGIN', '-DWLR_USE_UNSTABLE'], link_args: '-rdynamic') wstroke-2.2.1/src/resources.xml000066400000000000000000000002541472137506600165510ustar00rootroot00000000000000 gui.glade wstroke-2.2.1/src/stroke.c000066400000000000000000000151141472137506600154710ustar00rootroot00000000000000/* * Copyright (c) 2009, Thomas Jaeger * Copyright (c) 2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define _GNU_SOURCE #include "stroke.h" #include #include #include #include #include const double stroke_infinity = 0.2; #define EPS 0.000001 struct point { double x; double y; double t; double dt; double alpha; }; struct _stroke_t { int n; int capacity; struct point *p; }; stroke_t *stroke_alloc(int n) { assert(n > 0); stroke_t *s = malloc(sizeof(stroke_t)); s->n = 0; s->capacity = n; s->p = calloc(n, sizeof(struct point)); return s; } void stroke_add_point(stroke_t *s, double x, double y) { assert(s->capacity > s->n); s->p[s->n].x = x; s->p[s->n].y = y; s->n++; } static inline double angle_difference(double alpha, double beta) { double d = alpha - beta; if (d < -1.0) d += 2.0; else if (d > 1.0) d -= 2.0; return d; } void stroke_finish(stroke_t *s) { assert(s->capacity > 0); s->capacity = -1; int n = s->n - 1; double total = 0.0; s->p[0].t = 0.0; for (int i = 0; i < n; i++) { total += hypot(s->p[i+1].x - s->p[i].x, s->p[i+1].y - s->p[i].y); s->p[i+1].t = total; } for (int i = 0; i <= n; i++) s->p[i].t /= total; double minX = s->p[0].x, minY = s->p[0].y, maxX = minX, maxY = minY; for (int i = 1; i <= n; i++) { if (s->p[i].x < minX) minX = s->p[i].x; if (s->p[i].x > maxX) maxX = s->p[i].x; if (s->p[i].y < minY) minY = s->p[i].y; if (s->p[i].y > maxY) maxY = s->p[i].y; } double scaleX = maxX - minX; double scaleY = maxY - minY; double scale = (scaleX > scaleY) ? scaleX : scaleY; if (scale < 0.001) scale = 1; for (int i = 0; i <= n; i++) { s->p[i].x = (s->p[i].x-(minX+maxX)/2)/scale + 0.5; s->p[i].y = (s->p[i].y-(minY+maxY)/2)/scale + 0.5; } for (int i = 0; i < n; i++) { s->p[i].dt = s->p[i+1].t - s->p[i].t; s->p[i].alpha = atan2(s->p[i+1].y - s->p[i].y, s->p[i+1].x - s->p[i].x)/M_PI; } } void stroke_free(stroke_t *s) { if (s) free(s->p); free(s); } stroke_t *stroke_copy(const stroke_t *stroke) { if(!stroke) return NULL; stroke_t *s = malloc(sizeof(stroke_t)); if(!s) return NULL; s->p = calloc(stroke->n, sizeof(struct point)); if(!(s->p)) { free(s); return NULL; } s->n = stroke->n; s->capacity = s->n; memcpy(s->p, stroke->p, s->n * sizeof(struct point)); return s; } int stroke_get_size(const stroke_t *s) { return s->n; } void stroke_get_point(const stroke_t *s, int n, double *x, double *y) { assert(n < s->n); if (x) *x = s->p[n].x; if (y) *y = s->p[n].y; } double stroke_get_time(const stroke_t *s, int n) { assert(n < s->n); return s->p[n].t; } double stroke_get_angle(const stroke_t *s, int n) { assert(n+1 < s->n); return s->p[n].alpha; } inline static double sqr(double x) { return x*x; } double stroke_angle_difference(const stroke_t *a, const stroke_t *b, int i, int j) { return fabs(angle_difference(stroke_get_angle(a, i), stroke_get_angle(b, j))); } static inline void step(const stroke_t *a, const stroke_t *b, const int N, double *dist, int *prev_x, int *prev_y, const int x, const int y, const double tx, const double ty, int *k, const int x2, const int y2) { double dtx = a->p[x2].t - tx; double dty = b->p[y2].t - ty; if (dtx >= dty * 2.2 || dty >= dtx * 2.2 || dtx < EPS || dty < EPS) return; (*k)++; double d = 0.0; int i = x, j = y; double next_tx = (a->p[i+1].t - tx) / dtx; double next_ty = (b->p[j+1].t - ty) / dty; double cur_t = 0.0; for (;;) { double ad = sqr(angle_difference(a->p[i].alpha, b->p[j].alpha)); double next_t = next_tx < next_ty ? next_tx : next_ty; bool done = next_t >= 1.0 - EPS; if (done) next_t = 1.0; d += (next_t - cur_t)*ad; if (done) break; cur_t = next_t; if (next_tx < next_ty) next_tx = (a->p[++i+1].t - tx) / dtx; else next_ty = (b->p[++j+1].t - ty) / dty; } double new_dist = dist[x*N+y] + d * (dtx + dty); if (new_dist != new_dist) abort(); if (new_dist >= dist[x2*N+y2]) return; prev_x[x2*N+y2] = x; prev_y[x2*N+y2] = y; dist[x2*N+y2] = new_dist; } /* To compare two gestures, we use dynamic programming to minimize (an * approximation) of the integral over square of the angle difference among * (roughly) all reparametrizations whose slope is always between 1/2 and 2. */ double stroke_compare(const stroke_t *a, const stroke_t *b, int *path_x, int *path_y) { const int M = a->n; const int N = b->n; const int m = M - 1; const int n = N - 1; double* dist = malloc(M * N * sizeof(double)); int* prev_x = malloc(M * N * sizeof(int)); int* prev_y = malloc(M * N * sizeof(int)); for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) dist[i*N+j] = stroke_infinity; dist[M*N-1] = stroke_infinity; dist[0] = 0.0; for (int x = 0; x < m; x++) { for (int y = 0; y < n; y++) { if (dist[x*N+y] >= stroke_infinity) continue; double tx = a->p[x].t; double ty = b->p[y].t; int max_x = x; int max_y = y; int k = 0; while (k < 4) { if (a->p[max_x+1].t - tx > b->p[max_y+1].t - ty) { max_y++; if (max_y == n) { step(a, b, N, dist, prev_x, prev_y, x, y, tx, ty, &k, m, n); break; } for (int x2 = x+1; x2 <= max_x; x2++) step(a, b, N, dist, prev_x, prev_y, x, y, tx, ty, &k, x2, max_y); } else { max_x++; if (max_x == m) { step(a, b, N, dist, prev_x, prev_y, x, y, tx, ty, &k, m, n); break; } for (int y2 = y+1; y2 <= max_y; y2++) step(a, b, N, dist, prev_x, prev_y, x, y, tx, ty, &k, max_x, y2); } } } } double cost = dist[M*N-1]; if (path_x && path_y) { if (cost < stroke_infinity) { int x = m; int y = n; int k = 0; while (x || y) { int old_x = x; x = prev_x[x*N+y]; y = prev_y[old_x*N+y]; path_x[k] = x; path_y[k] = y; k++; } } else { path_x[0] = 0; path_y[0] = 0; } } free(prev_y); free(prev_x); free(dist); return cost; } wstroke-2.2.1/src/stroke.h000066400000000000000000000032321472137506600154740ustar00rootroot00000000000000/* * Copyright (c) 2009, Thomas Jaeger * Copyright (c) 2023, Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __STROKE_H__ #define __STROKE_H__ #ifdef __cplusplus extern "C" { #endif struct _stroke_t; typedef struct _stroke_t stroke_t; stroke_t *stroke_alloc(int n); void stroke_add_point(stroke_t *stroke, double x, double y); void stroke_finish(stroke_t *stroke); void stroke_free(stroke_t *stroke); stroke_t *stroke_copy(const stroke_t *stroke); int stroke_get_size(const stroke_t *stroke); void stroke_get_point(const stroke_t *stroke, int n, double *x, double *y); double stroke_get_time(const stroke_t *stroke, int n); double stroke_get_angle(const stroke_t *stroke, int n); double stroke_angle_difference(const stroke_t *a, const stroke_t *b, int i, int j); double stroke_compare(const stroke_t *a, const stroke_t *b, int *path_x, int *path_y); extern const double stroke_infinity; #ifdef __cplusplus } #endif #endif wstroke-2.2.1/src/stroke_draw.cc000066400000000000000000000075071472137506600166600ustar00rootroot00000000000000/* * stroke_draw.cc * * Copyright (c) 2008-2009, Thomas Jaeger * Copyright (c) 2020-2023 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "stroke_draw.h" #include Glib::RefPtr StrokeDrawer::pbEmpty; Glib::RefPtr StrokeDrawer::drawEmpty(int size) { if (size != STROKE_SIZE) return drawEmpty_(size); if (pbEmpty) return pbEmpty; pbEmpty = drawEmpty_(size); return pbEmpty; } Glib::RefPtr StrokeDrawer::draw(const Stroke* stroke, int size, double width) { Glib::RefPtr pb = drawEmpty_(size); int w = size; int h = size; int stride = pb->get_rowstride(); guint8 *row = pb->get_pixels(); // This is all pretty messed up // http://www.archivum.info/gtkmm-list@gnome.org/2007-05/msg00112.html Cairo::RefPtr surface = Cairo::ImageSurface::create(row, Cairo::FORMAT_ARGB32, w, h, stride); draw(stroke, surface, 0, 0, pb->get_width(), size, width); for (int i = 0; i < w; i++) { guint8 *px = row; for (int j = 0; j < h; j++) { guint8 a = px[3]; guint8 r = px[2]; guint8 g = px[1]; guint8 b = px[0]; if (a) { px[0] = ((((guint)r) << 8) - r) / a; px[1] = ((((guint)g) << 8) - g) / a; px[2] = ((((guint)b) << 8) - b) / a; } px += 4; } row += stride; } return pb; } Glib::RefPtr StrokeDrawer::drawEmpty_(int size) { Glib::RefPtr pb = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB,true,8,size,size); pb->fill(0x00000000); return pb; } void StrokeDrawer::draw(const Stroke* stroke, Cairo::RefPtr surface, int x, int y, int w, int h, double width) { const Cairo::RefPtr ctx = Cairo::Context::create (surface); x += width; y += width; w -= 2*width; h -= 2*width; ctx->save(); ctx->translate(x,y); ctx->scale(w,h); ctx->set_line_width(2.0*width/(w+h)); if (stroke->size()) { ctx->set_line_cap(Cairo::LINE_CAP_ROUND); int n = stroke->size(); float lambda = sqrt(3)-2.0; float sum = lambda / (1 - lambda); std::vector y(n); y[0] = stroke->points(0) * sum; for (int j = 0; j < n-1; j++) y[j+1] = (y[j] + stroke->points(j)) * lambda; std::vector z(n); z[n-1] = stroke->points(n-1) * (-sum); for (int j = n-1; j > 0; j--) z[j-1] = (z[j] - stroke->points(j)) * lambda; for (int j = 0; j < n-1; j++) { // j -> j+1 ctx->set_source_rgba(0.0, stroke->time(j), 1.0-stroke->time(j), 1.0); Stroke::Point p[4]; p[0] = stroke->points(j); p[3] = stroke->points(j+1); p[1] = p[0] + y[j] + z[j]; p[2] = p[3] - y[j+1] - z[j+1]; ctx->move_to(p[0].x, p[0].y); ctx->curve_to(p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y); ctx->stroke(); } } else { ctx->set_source_rgba(0.0, 0.0, 1.0, 1.0); ctx->move_to(0.33, 0.33); ctx->line_to(0.67, 0.67); ctx->move_to(0.33, 0.67); ctx->line_to(0.67, 0.33); ctx->stroke(); } ctx->restore(); } void StrokeDrawer::draw_svg(const Stroke* stroke, std::string filename) { const int S = 32; const int B = 1; Cairo::RefPtr s = Cairo::SvgSurface::create(filename, S, S); draw(stroke, s, B, B, S-2*B, S-2*B); } wstroke-2.2.1/src/stroke_draw.h000066400000000000000000000027021472137506600165120ustar00rootroot00000000000000/* * stroke_draw.h * * Copyright (c) 2008-2009, Thomas Jaeger * Copyright (c) 2020-2023 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #ifndef STROKE_DRAW_H #define STROKE_DRAW_H #include "gesture.h" #include class StrokeDrawer { protected: static Glib::RefPtr drawEmpty_(int); static Glib::RefPtr pbEmpty; public: static Glib::RefPtr draw(const Stroke* stroke, int size, double width = 2.0); static void draw(const Stroke* stroke, Cairo::RefPtr surface, int x, int y, int w, int h, double width = 2.0); static void draw_svg(const Stroke* stroke, std::string filename); static Glib::RefPtr drawEmpty(int); }; #endif wstroke-2.2.1/src/stroke_drawing_area.cpp000066400000000000000000000055131472137506600205360ustar00rootroot00000000000000/* * stroke_drawing_area.cpp -- Gtk.DrawingArea adapted to record new strokes * * Copyright 2020-2023 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "stroke_drawing_area.h" #include SRArea::SRArea() { // signal_configure_event().connect(sigc::mem_fun(*this, &SRArea::configure_event)); add_events(Gdk::EventMask::BUTTON_PRESS_MASK | Gdk::EventMask::BUTTON_RELEASE_MASK | Gdk::EventMask::BUTTON_MOTION_MASK); } bool SRArea::on_configure_event(GdkEventConfigure* event) { auto win = get_window(); surface = win->create_similar_surface(Cairo::Content::CONTENT_COLOR, event->width, event->height); clear(); return true; } bool SRArea::on_draw(const Cairo::RefPtr& cr) { if(surface) { cr->set_source(surface, 0, 0); cr->paint(); } return true; } bool SRArea::on_button_press_event(GdkEventButton* event) { if(current_button) return true; current_button = event->button; last_x = event->x; last_y = event->y; ps.clear(); stroke = Stroke(); ps.push_back(Stroke::Point {last_x, last_y}); return true; } bool SRArea::on_button_release_event(GdkEventButton* event) { if(event->button != current_button) return true; draw_line(event->x, event->y); current_button = 0; stroke = Stroke(ps); ps.clear(); stroke_recorded.emit(&stroke); return true; } bool SRArea::on_motion_notify_event(GdkEventMotion* event) { if(current_button) draw_line(event->x, event->y); return true; } void SRArea::draw_line(gdouble x, gdouble y) { if(surface && (x != last_x || y != last_y)) { auto cr = Cairo::Context::create(surface); cr->set_source_rgb(0.8, 0, 0); cr->move_to(last_x, last_y); cr->line_to(x, y); cr->stroke(); int x1 = std::floor(std::min(x, last_x)) - 2; int y1 = std::floor(std::min(y, last_y)) - 2; int w = std::ceil(std::abs(x - last_x)) + 4; int h = std::ceil(std::abs(y - last_y)) + 4; queue_draw_area(x1, y1, w, h); ps.push_back(Stroke::Point {last_x, last_y}); last_x = x; last_y = y; } } void SRArea::clear() { if(surface) { auto cr = Cairo::Context::create(surface); cr->set_source_rgb(1, 1, 1); cr->paint(); } ps.clear(); stroke = Stroke(); } wstroke-2.2.1/src/stroke_drawing_area.h000066400000000000000000000032641472137506600202040ustar00rootroot00000000000000/* * stroke_drawing_area.h -- Gtk.DrawingArea adapted to record new strokes * * Copyright 2020-2023 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #ifndef STROKE_DRAWING_AREA_H #define STROKE_DRAWING_AREA_H #include #include "gesture.h" class SRArea : public Gtk::DrawingArea { public: SRArea(); void clear(); Stroke* get_stroke() { return &stroke; } sigc::signal stroke_recorded; virtual ~SRArea() { } protected: bool on_draw(const Cairo::RefPtr& cr) override; bool on_button_press_event(GdkEventButton* event) override; bool on_button_release_event(GdkEventButton* event) override; bool on_motion_notify_event(GdkEventMotion* event) override; bool on_configure_event(GdkEventConfigure* event) override; void draw_line(gdouble x, gdouble y); Cairo::RefPtr surface; guint current_button = 0; gdouble last_x; gdouble last_y; Stroke::PreStroke ps; Stroke stroke; }; #endif wstroke-2.2.1/toplevel-grabber/000077500000000000000000000000001472137506600164615ustar00rootroot00000000000000wstroke-2.2.1/toplevel-grabber/meson.build000066400000000000000000000021061472137506600206220ustar00rootroot00000000000000# toplevel-grabber library and example program grabber_protocols = [ './wlr-foreign-toplevel-management-unstable-v1.xml' ] grabber_protos_client_src = [] grabber_protos_headers = [] foreach p : grabber_protocols xml = join_paths(p) grabber_protos_headers += wayland_scanner_client.process(xml) grabber_protos_client_src += wayland_scanner_code.process(xml) endforeach lib_grabber_protos = static_library('grabber_protos', grabber_protos_client_src + grabber_protos_headers, dependencies: [wayland_client]) # for the include directory lib_grabber_protos_dep = declare_dependency( link_with: lib_grabber_protos, sources: grabber_protos_headers, ) toplevel_grabber = static_library('toplevel_grabber', 'toplevel-grabber.c', dependencies: [wayland_client, lib_grabber_protos_dep], c_args: ['-D_POSIX_C_SOURCE=200809L']) toplevel_grabber_dep = declare_dependency( link_with: toplevel_grabber, include_directories: include_directories('.') ) toplevel_grabber_test = executable('tl_grabber_test', 'toplevel-grabber-test.c', dependencies: [toplevel_grabber_dep], install: false) wstroke-2.2.1/toplevel-grabber/toplevel-grabber-test.c000066400000000000000000000033561472137506600230450ustar00rootroot00000000000000/* * toplevel-grabber-test.c -- test selecting a toplevel view based on * user interaction * * Copyright 2020 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include #include #include #include static int is_done = 0; static void tl_cb(void* data, struct tl_grabber* gr) { char* app_id = toplevel_grabber_get_app_id(gr); printf("Activated app: %s\n", app_id ? app_id : "(null)"); if(app_id) free(app_id); toplevel_grabber_free(gr); is_done = 1; } int main() { struct wl_display* dpy = wl_display_connect(NULL); if(!dpy) { fprintf(stderr, "Cannot connect to display!\n"); return 1; } struct tl_grabber* gr = toplevel_grabber_new(dpy, NULL, NULL); if(!gr) { fprintf(stderr, "Cannot create grabber interface!\n"); return 1; } printf("Starting grabber, click to select a toplevel view\n"); toplevel_grabber_set_callback(gr, tl_cb, NULL); while(!(is_done || wl_display_dispatch(dpy) == -1)); if(!is_done) toplevel_grabber_free(gr); return 0; } wstroke-2.2.1/toplevel-grabber/toplevel-grabber.c000066400000000000000000000207521472137506600220670ustar00rootroot00000000000000/* * toplevel-grabber.c -- library using the wlr-foreign-toplevel * interface to get the ID of an activated toplevel view * * Copyright 2020 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include #include #include #include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" #include typedef struct zwlr_foreign_toplevel_handle_v1 wfthandle; /* struct to hold information for one instance of the grabber */ struct tl_grabber { struct zwlr_foreign_toplevel_manager_v1* manager; struct wl_list toplevels; void (*callback)(void* data, struct tl_grabber* gr); void* data; struct toplevel* active; int init_done; }; /* struct to hold information about one toplevel */ struct toplevel { char* app_id; wfthandle* handle; wfthandle* parent; struct tl_grabber* gr; int init_done; struct wl_list link; }; #ifndef G_GNUC_UNUSED #define G_GNUC_UNUSED __attribute__((unused)) #endif /* callbacks */ static void title_cb(G_GNUC_UNUSED void* data, G_GNUC_UNUSED wfthandle* handle, G_GNUC_UNUSED const char* title) { /* don't care */ } static void appid_cb(void* data, G_GNUC_UNUSED wfthandle* handle, const char* app_id) { if(!(app_id && data)) return; struct toplevel* tl = (struct toplevel*)data; if(tl->app_id) free(tl->app_id); tl->app_id = strdup(app_id); } void output_enter_cb(G_GNUC_UNUSED void* data, G_GNUC_UNUSED wfthandle* handle, G_GNUC_UNUSED struct wl_output* output) { /* don't care */ } void output_leave_cb(G_GNUC_UNUSED void* data, G_GNUC_UNUSED wfthandle* handle, G_GNUC_UNUSED struct wl_output* output) { /* don't care */ } void state_cb(void* data, G_GNUC_UNUSED wfthandle* handle, struct wl_array* state) { if(!(data && state)) return; struct toplevel* tl = (struct toplevel*)data; struct tl_grabber* gr = tl->gr; if(!gr) return; int activated = 0; int i; uint32_t* stdata = (uint32_t*)state->data; for(i = 0; i*sizeof(uint32_t) < state->size; i++) { if(stdata[i] == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) { activated = 1; break; } } if(activated) { int orig_init_done = tl->init_done; struct toplevel* new_active = NULL; while(tl) { new_active = tl; if(!tl->parent) break; tl = zwlr_foreign_toplevel_handle_v1_get_user_data(tl->parent); } if(new_active != gr->active) { gr->active = new_active; if(orig_init_done && gr->callback) gr->callback(gr->data, gr); } } } void done_cb(void* data, G_GNUC_UNUSED wfthandle* handle) { if(!data) return; struct toplevel* tl = (struct toplevel*)data; tl->init_done = 1; } void closed_cb(void* data, G_GNUC_UNUSED wfthandle* handle) { if(!data) return; struct toplevel* tl = (struct toplevel*)data; /* note: we can assume that this toplevel is not set as the parent * of any existing toplevels at this point */ wl_list_remove(&(tl->link)); zwlr_foreign_toplevel_handle_v1_destroy(tl->handle); if(tl->app_id) free(tl->app_id); free(tl); } void parent_cb(void* data, G_GNUC_UNUSED wfthandle* handle, wfthandle* parent) { if(!data) return; struct toplevel* tl = (struct toplevel*)data; tl->parent = parent; } struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_interface = { .title = title_cb, .app_id = appid_cb, .output_enter = output_enter_cb, .output_leave = output_leave_cb, .state = state_cb, .done = done_cb, .closed = closed_cb, .parent = parent_cb }; /* register new toplevel */ static void new_toplevel(void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_manager_v1 *manager, wfthandle *handle) { if(!handle) return; if(!data) { /* if we unset the user data pointer, then we don't care anymore */ zwlr_foreign_toplevel_handle_v1_destroy(handle); return; } struct tl_grabber* gr = (struct tl_grabber*)data; struct toplevel* tl = (struct toplevel*)malloc(sizeof(struct toplevel)); if(!tl) { /* TODO: error message */ return; } tl->app_id = NULL; tl->handle = handle; tl->parent = NULL; tl->init_done = 0; tl->gr = gr; wl_list_insert(&(gr->toplevels), &(tl->link)); /* note: we cannot do anything as long as we get app_id */ zwlr_foreign_toplevel_handle_v1_add_listener(handle, &toplevel_handle_interface, tl); } /* sent when toplevel management is no longer available -- this will happen after stopping */ static void toplevel_manager_finished(G_GNUC_UNUSED void *data, struct zwlr_foreign_toplevel_manager_v1 *manager) { zwlr_foreign_toplevel_manager_v1_destroy(manager); } static struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_interface = { .toplevel = new_toplevel, .finished = toplevel_manager_finished, }; static void registry_global_add_cb(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct tl_grabber* gr = (struct tl_grabber*)data; if(!strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name)) { uint32_t v = zwlr_foreign_toplevel_manager_v1_interface.version; if(version < v) v = version; gr->manager = wl_registry_bind(registry, id, &zwlr_foreign_toplevel_manager_v1_interface, v); if(gr->manager) zwlr_foreign_toplevel_manager_v1_add_listener(gr->manager, &toplevel_manager_interface, gr); else { /* TODO: handle error */ } } gr->init_done = 0; } static void registry_global_remove_cb(G_GNUC_UNUSED void *data, G_GNUC_UNUSED struct wl_registry *registry, G_GNUC_UNUSED uint32_t id) { /* don't care */ } static const struct wl_registry_listener registry_listener = { registry_global_add_cb, registry_global_remove_cb }; struct tl_grabber* toplevel_grabber_new(struct wl_display* dpy, void (*callback)(void* data, struct tl_grabber* gr), void* data) { if(!dpy) { /* TODO: get display! */ return NULL; } struct tl_grabber* gr = (struct tl_grabber*)malloc(sizeof(struct tl_grabber)); if(!gr) return NULL; gr->manager = NULL; wl_list_init(&(gr->toplevels)); gr->callback = callback; gr->data = data; gr->active = NULL; struct wl_registry* registry = wl_display_get_registry(dpy); wl_registry_add_listener(registry, ®istry_listener, gr); do { gr->init_done = 1; wl_display_roundtrip(dpy); } while(!gr->init_done); return gr; } char* toplevel_grabber_get_app_id(struct tl_grabber* gr) { char* ret = NULL; if(gr && gr->active && gr->active->app_id) ret = strdup(gr->active->app_id); return ret; } /* void toplevel_grabber_reset(struct tl_grabber* gr) { if(gr) gr->active = NULL; } */ void toplevel_grabber_set_callback(struct tl_grabber* gr, void (*callback)(void* data, struct tl_grabber* gr), void* data) { if(gr) { gr->callback = callback; gr->data = data; } } int toplevel_grabber_activate_app(struct tl_grabber* gr, const char* app_id, struct wl_seat* wl_seat, int parent) { if(!(gr && app_id)) return -1; struct toplevel* tl; wl_list_for_each(tl, &(gr->toplevels), link) { if(!strcmp(app_id, tl->app_id)) { if(parent) while(tl->parent) { struct toplevel* tmp = zwlr_foreign_toplevel_handle_v1_get_user_data(tl->parent); if(!tmp) break; tl = tmp; } zwlr_foreign_toplevel_handle_v1_activate (tl->handle, wl_seat); return 0; } } return -1; } void toplevel_grabber_free(struct tl_grabber* gr) { if(!gr) return; /* stop listening and also free all existing toplevels */ /* set user data to null -- this will stop adding newly reported toplevels */ zwlr_foreign_toplevel_manager_v1_set_user_data(gr->manager, NULL); /* this will send the finished signal and result in destroying manager later */ zwlr_foreign_toplevel_manager_v1_stop(gr->manager); gr->manager = NULL; /* destroy all existing toplevel handles */ struct toplevel* tl; struct toplevel* tmp; wl_list_for_each_safe(tl, tmp, &(gr->toplevels), link) { wl_list_remove(&(tl->link)); zwlr_foreign_toplevel_handle_v1_destroy(tl->handle); if(tl->app_id) free(tl->app_id); free(tl); } } wstroke-2.2.1/toplevel-grabber/toplevel-grabber.h000066400000000000000000000054501472137506600220720ustar00rootroot00000000000000/* * toplevel-grabber.h -- library using the wlr-foreign-toplevel * interface to get the ID of an activated toplevel view * * Copyright 2020 Daniel Kondor * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #ifndef TOPLEVEL_GRABBER_H #define TOPLEVEL_GRABBER_H #ifdef __cplusplus extern "C" { #endif #include struct tl_grabber; /* * Create a new grabber and start listening to events about toplevels. * Parameters: * dpy -- Wayland display connectoin to use (must be given by the caller) * callback -- function to be called when a new toplevel is activated (can be NULL) * data -- user data to pass to the callback function */ struct tl_grabber* toplevel_grabber_new(struct wl_display* dpy, void (*callback)(void* data, struct tl_grabber* gr), void* data); /* * Get the last activated app ID (if any). Returns a copy of the app ID, * or NULL if no app was activated or no app ID can be determined. If the * return value is non-NULL, the caller must free() it. */ char* toplevel_grabber_get_app_id(struct tl_grabber* gr); /* * Reset the currently active app, i.e. toplevel_grabber_get_app_id() * will return NULL until a new toplevel is activated again. * void toplevel_grabber_reset(struct tl_grabber* gr); */ /* * Set the callback function to be called when a new toplevel is activated. */ void toplevel_grabber_set_callback(struct tl_grabber* gr, void (*callback)(void* data, struct tl_grabber* gr), void* data); /* * Activate any toplevel view with a matching app_id on the given seat. * If parent is true, it selects the topmost parent if multiple views * in a hierarchy have the same app ID. * This is useful to re-show the caller's view after the user has * selected another app by activating it. * Returns 0 on success or -1 if the given app ID was not found. */ int toplevel_grabber_activate_app(struct tl_grabber* gr, const char* app_id, struct wl_seat* wl_seat, int parent); /* Stop listening to toplevel events and free all resources associated * with this instance. */ void toplevel_grabber_free(struct tl_grabber* gr); #ifdef __cplusplus } #endif #endif wstroke-2.2.1/toplevel-grabber/wlr-foreign-toplevel-management-unstable-v1.xml000066400000000000000000000263021472137506600275420ustar00rootroot00000000000000 Copyright © 2018 Ilia Bozhinov Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. The purpose of this protocol is to enable the creation of taskbars and docks by providing them with a list of opened applications and letting them request certain actions on them, like maximizing, etc. After a client binds the zwlr_foreign_toplevel_manager_v1, each opened toplevel window will be sent via the toplevel event This event is emitted whenever a new toplevel window is created. It is emitted for all toplevels, regardless of the app that has created them. All initial details of the toplevel(title, app_id, states, etc.) will be sent immediately after this event via the corresponding events in zwlr_foreign_toplevel_handle_v1. Indicates the client no longer wishes to receive events for new toplevels. However the compositor may emit further toplevel_created events, until the finished event is emitted. The client must not send any more requests after this one. This event indicates that the compositor is done sending events to the zwlr_foreign_toplevel_manager_v1. The server will destroy the object immediately after sending this request, so it will become invalid and the client should free any resources associated with it. A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel window. Each app may have multiple opened toplevels. Each toplevel has a list of outputs it is visible on, conveyed to the client with the output_enter and output_leave events. This event is emitted whenever the title of the toplevel changes. This event is emitted whenever the app-id of the toplevel changes. This event is emitted whenever the toplevel becomes visible on the given output. A toplevel may be visible on multiple outputs. This event is emitted whenever the toplevel stops being visible on the given output. It is guaranteed that an entered-output event with the same output has been emitted before this event. Requests that the toplevel be maximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be unmaximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be minimized. If the minimized state actually changes, this will be indicated by the state event. Requests that the toplevel be unminimized. If the minimized state actually changes, this will be indicated by the state event. Request that this toplevel be activated on the given seat. There is no guarantee the toplevel will be actually activated. The different states that a toplevel can have. These have the same meaning as the states with the same names defined in xdg-toplevel This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 is created and each time the toplevel state changes, either because of a compositor action or because of a request in this protocol. This event is sent after all changes in the toplevel state have been sent. This allows changes to the zwlr_foreign_toplevel_handle_v1 properties to be seen as atomic, even if they happen via multiple events. Send a request to the toplevel to close itself. The compositor would typically use a shell-specific method to carry out this request, for example by sending the xdg_toplevel.close event. However, this gives no guarantees the toplevel will actually be destroyed. If and when this happens, the zwlr_foreign_toplevel_handle_v1.closed event will be emitted. The rectangle of the surface specified in this request corresponds to the place where the app using this protocol represents the given toplevel. It can be used by the compositor as a hint for some operations, e.g minimizing. The client is however not required to set this, in which case the compositor is free to decide some default value. If the client specifies more than one rectangle, only the last one is considered. The dimensions are given in surface-local coordinates. Setting width=height=0 removes the already-set rectangle. This event means the toplevel has been destroyed. It is guaranteed there won't be any more events for this zwlr_foreign_toplevel_handle_v1. The toplevel itself becomes inert so any requests will be ignored except the destroy request. Destroys the zwlr_foreign_toplevel_handle_v1 object. This request should be called either when the client does not want to use the toplevel anymore or after the closed event to finalize the destruction of the object. Requests that the toplevel be fullscreened on the given output. If the fullscreen state and/or the outputs the toplevel is visible on actually change, this will be indicated by the state and output_enter/leave events. The output parameter is only a hint to the compositor. Also, if output is NULL, the compositor should decide which output the toplevel will be fullscreened on, if at all. Requests that the toplevel be unfullscreened. If the fullscreen state actually changes, this will be indicated by the state event. This event is emitted whenever the parent of the toplevel changes. wstroke-2.2.1/wstroke-config.1000066400000000000000000000011221472137506600162440ustar00rootroot00000000000000.TH WSTROKE-CONFIG 1 2024-08-11 .SH NAME wstroke-config \- graphical configuration of mouse gestures .SH SYNOPSIS .BR wstroke-config .SH DESCRIPTION .BR wstroke-config is a graphical utility to configure gestures recognized by .BR wstroke , a mouse gesture plugin for the .BR wayfire (1) compositor. Note that additional options that affect the behavior of .BR wstroke are configured using wayfire's regular config system, e.g. by .BR wcm. .SH ENVIRONMENT VARIABLES .BR wstroke-config respects the XDG_CONFIG_HOME environment variable: configuration is stored under ${XDG_CONFIG_HOME}/wstroke. wstroke-2.2.1/wstroke-config.desktop000066400000000000000000000003531472137506600175620ustar00rootroot00000000000000[Desktop Entry] Version=1.0 Name=WStroke config Type=Application Terminal=false Exec=wstroke-config Icon=wstroke Categories=GTK;Utility;Accessibility; Comment=Configure mouse gestures used with Wayfire Keywords=gestures;input;wayfire; wstroke-2.2.1/wstroke.xml000066400000000000000000000066671472137506600154640ustar00rootroot00000000000000 <_short>Mouse Gestures <_long>A plugin to identify complex gestures drawn by the mouse and associate them with actions. Accessibility <_short>General <_short>Action preferences