init
This commit is contained in:
commit
d2743fe41d
22 changed files with 2290 additions and 0 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
lib/*.so
|
||||
|
||||
docs/*.3
|
||||
docs/*.1
|
||||
|
||||
misc/
|
||||
|
||||
373
LICENSE
Normal file
373
LICENSE
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
26
README.md
Normal file
26
README.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# thornWM
|
||||
|
||||
A toy X11 WM I never got around to finishing. I first wanted a manuel tiler with configuration similar to awesomeWM, then I later decided I prefered auto, then after switching the tiling mechanics I decided it was saner to slim down awesomeWM and never finished this but it is kinda functional
|
||||
|
||||

|
||||
|
||||
## Requirements
|
||||
* [Lua](https://www.lua.org/)[[jit](https://luajit.org/)] 5.1+
|
||||
* [Lua-ev](https://github.com/brimworks/lua-ev) ([LuaRocks](https://luarocks.org/modules/brimworks/lua-ev))
|
||||
Additionally, the default config makes use of:
|
||||
* a terminal emulator
|
||||
* dmenu
|
||||
* lemonbar
|
||||
* xsetroot
|
||||
|
||||
## Installation
|
||||
* `git clone https://git.sylvie.moe/tanya/thornWM && cd thornWM`
|
||||
* Optionally edit the makefile
|
||||
* `make install`
|
||||
|
||||
## Thanks
|
||||
* i3
|
||||
* dwm
|
||||
* awesomeWM
|
||||
* Shaver McCoy (RIP)
|
||||
|
||||
158
config.lua
Normal file
158
config.lua
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
-- This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ --
|
||||
local Async, Tree = require("async"), require("tree")
|
||||
local X, C, Conf = require("x"), require("c"), require("conf")
|
||||
local Util = require("util")
|
||||
local tilers = require("tree.tilers")
|
||||
|
||||
-- {{{ Settings
|
||||
|
||||
os.setlocale("") -- Set the locale to $LANG
|
||||
Conf.shell = "sh" -- Used by Async.spawnShell
|
||||
local term = "konsole"
|
||||
|
||||
local theme = {
|
||||
gap = 5,
|
||||
margin = 5,
|
||||
borderWidth = 2,
|
||||
defaultBorderColour = 0x444444,
|
||||
focusedBorderColour = 0xaaaaaa,
|
||||
}
|
||||
|
||||
local tilerStack = {
|
||||
tilers.masterLeft, tilers.masterRight,
|
||||
tilers.masterTop, tilers.masterBottom,
|
||||
getNext = Util.getNext -- Convenience to do tiler:getNext()
|
||||
}
|
||||
for t,tiler in ipairs(tilerStack) do
|
||||
tiler:setGap(theme.gap)
|
||||
end
|
||||
|
||||
-- }}}
|
||||
|
||||
-- {{{ Events
|
||||
|
||||
Conf.on("newMonitor", function(mon)
|
||||
mon.tiler = tilerStack[1]
|
||||
mon:setMargins({ top = theme.margin, bottom = theme.margin, left = theme.margin, right = theme.margin })
|
||||
end)
|
||||
Conf.on("newWindow", function(win)
|
||||
if win.managed then
|
||||
win:focus()
|
||||
end
|
||||
end)
|
||||
Conf.on("delWindow", function(win)
|
||||
if win.managed then
|
||||
if win.focused and #Tree.focusedMonitor.stack > 1 then
|
||||
win:getNext(-1):focus()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- }}}
|
||||
|
||||
-- {{{ Bar
|
||||
|
||||
local barStdin = Async.spawn("lemonbar", "-B", "#111111", "-f", "Source Code Pro 50", "-g", "1920x25+0+0")
|
||||
Async.timer(0.0, 1.0, function() -- Start after 0 seconds, repeat after 1 second
|
||||
local tagStr = "[1][2][3][4][5][6][7][8][9][0]"
|
||||
barStdin:write(string.format(" %s%%{r}%s \n", tagStr, os.date("%a %b %d %H:%M:%S")))
|
||||
end)
|
||||
|
||||
-- }}}
|
||||
|
||||
-- {{{ Keybindings
|
||||
|
||||
-- See https://wiki.linuxquestions.org/wiki/List_of_keysyms for all keysyms
|
||||
local shift, ctrl, alt, super = X.Shift, X.Control, X.Mod1, X.Mod2
|
||||
local master = alt
|
||||
|
||||
-- Meta --
|
||||
Conf.onKeyPress("r", master + shift, function()
|
||||
Conf.init()
|
||||
end)
|
||||
Conf.onKeyPress("q", master + shift, function()
|
||||
os.exit(0)
|
||||
end)
|
||||
|
||||
-- Spawn --
|
||||
Conf.onKeyPress("Return", master, function()
|
||||
Async.spawn(term)
|
||||
end)
|
||||
Conf.onKeyPress("p", master, function()
|
||||
Async.spawn("dmenu_run", "-l", "20", "-p", "Run: ")
|
||||
end)
|
||||
|
||||
-- Tiling --
|
||||
local function focus(ev)
|
||||
if #Tree.focusedMonitor.stack < 2 then return end
|
||||
local d = ({ j = 1, k = -1 })[ev.key]
|
||||
Tree.focusedWindow:getNext(d):focus()
|
||||
end
|
||||
Conf.onKeyPress("j", master, focus)
|
||||
Conf.onKeyPress("k", master, focus)
|
||||
|
||||
local function swap(ev)
|
||||
local stack = Tree.focusedMonitor.stack
|
||||
if #stack < 2 then return end
|
||||
local d = ({ j = 1, k = -1 })[ev.key]
|
||||
local cur = Tree.focusedWindow
|
||||
local curi = cur:getIndex()
|
||||
local newi = cur:getNext(d):getIndex()
|
||||
stack[curi], stack[newi] = stack[newi], stack[curi]
|
||||
Tree.focusedMonitor:updateTiling()
|
||||
end
|
||||
Conf.onKeyPress("j", master + shift, swap)
|
||||
Conf.onKeyPress("k", master + shift, swap)
|
||||
|
||||
local function layout(ev)
|
||||
local d = ({ [master] = 1, [master+shift] = -1 })[ev.mask]
|
||||
local fm = Tree.focusedMonitor
|
||||
fm.tiler = tilerStack:getNext(fm.tiler, d)
|
||||
fm:updateTiling()
|
||||
end
|
||||
Conf.onKeyPress("space", master, layout)
|
||||
Conf.onKeyPress("space", master + shift, layout)
|
||||
|
||||
-- Tags --
|
||||
-- for t,tag in pairs(tags) do
|
||||
-- Conf.onKeyPress(tag.key, master, function()
|
||||
-- -- print(tag.id)
|
||||
-- Tree.tags.default:hide()
|
||||
-- end)
|
||||
-- end
|
||||
|
||||
-- Misc --
|
||||
-- Conf.onKeyPress("space", master, function() -- Lists all windows in a dmenu prompt
|
||||
-- local windows, rwindows = {}, {}
|
||||
-- for w,win in pairs(Tree.windows) do
|
||||
-- local name = win:getName()
|
||||
-- if name and not rwindows[name] then
|
||||
-- table.insert(windows, name)
|
||||
-- rwindows[name] = win
|
||||
-- end
|
||||
-- end
|
||||
-- Async.spawn("dmenu", function(out, err, status)
|
||||
-- print(out)
|
||||
-- end):write(table.concat(windows, "\n")):close()
|
||||
-- end)
|
||||
|
||||
-- }}}
|
||||
|
||||
-- {{{ Autostart
|
||||
|
||||
Async.spawn("xcompmgr")
|
||||
Async.timer(0.1, function()
|
||||
Async.spawn("st")
|
||||
end)
|
||||
Async.spawn("xsetroot", "-cursor_name", "left_ptr")
|
||||
Async.spawn("/usr/bin/feh", "--no-fehbg", "--bg-fill", '/home/eiko/Niðurhal/anime/veggfóður/cirnohehe.png')
|
||||
-- Async.spawnShell([[
|
||||
-- setxkbmap -model pc105 -layout is,ru-is -option caps:escape grp:rctrl_switch grp:shift_caps_toggle compose:sclk shift:both_capslock >/dev/null
|
||||
-- xset r rate 300 35
|
||||
-- ]])
|
||||
|
||||
-- }}}
|
||||
|
||||
-- Async.readFile("./thornWM", print)
|
||||
|
||||
-- vim: fdm=marker:noet
|
||||
114
docs/genDocs
Executable file
114
docs/genDocs
Executable file
|
|
@ -0,0 +1,114 @@
|
|||
#!/bin/env lua
|
||||
-- This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ --
|
||||
-- Any documentation generated is licensed under PDL-v1 https://www.openoffice.org/licenses/PDL.html
|
||||
local sourceFileName = assert(arg[1])
|
||||
local textFileName = assert(arg[2])
|
||||
|
||||
local sourceFile = assert(io.open(sourceFileName))
|
||||
local tf = assert(io.open(textFileName, "w"))
|
||||
|
||||
local cmdArgs = {}
|
||||
for i=1,#arg do
|
||||
if skipNext then
|
||||
skipNext = false
|
||||
else
|
||||
local a = arg[i]
|
||||
if a == "-e" or a == "--ext" then
|
||||
cmdArgs.ext = arg[i+1]
|
||||
skipNext = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local ext = cmdArgs.ext or sourceFileName:match("[^.]+$")
|
||||
local marker
|
||||
|
||||
if ext == "c" then
|
||||
marker = "//%*"
|
||||
elseif ext == "lua" then
|
||||
marker = "--%*"
|
||||
else
|
||||
error("Unkown extension", ext)
|
||||
end
|
||||
|
||||
local name = sourceFileName:match("([^/]+)%.%w+$")
|
||||
local functions = {}
|
||||
local description = {}
|
||||
|
||||
function bold(str)
|
||||
return "\\fB"..str.."\\fR"
|
||||
end
|
||||
function italic(str)
|
||||
return "\\fI"..str.."\\fR"
|
||||
end
|
||||
|
||||
for line in sourceFile:lines() do
|
||||
local s, e = line:find("^%s*"..marker.."%s*")
|
||||
if s then
|
||||
local str = line:sub(e+1)
|
||||
s, e = str:find("%w%([^)]*%)")
|
||||
if s then
|
||||
local proto = str:sub(1, e)
|
||||
local returnProto = ""
|
||||
local des = str:sub(e+2)
|
||||
if proto:match("^[%w|,[%]:.]+ ") then
|
||||
returnProto = proto:match("^([^(]+) ")--:gsub("%w+", italic("%1")):gsub("%|", " | "):gsub(",", ", ").." "
|
||||
returnProto = returnProto:gsub("^(%w+)", bold("%1")):gsub("| (%w+)", "| "..bold("%1")):gsub(", (%w+)", ", "..bold("%1")):gsub("%[(%w+)", "["..bold("%1"))
|
||||
returnProto = returnProto:gsub(" %w+", italic("%1"))
|
||||
returnProto = returnProto.." "
|
||||
proto = proto:gsub("^([^(]+) (%w+)", "%2")
|
||||
end
|
||||
proto = proto:gsub("%w+[,)%]]?", function(word)
|
||||
if word:match("[,)%]]") then
|
||||
return italic(word:sub(1, -2)) .. word:sub(-1)
|
||||
else
|
||||
return bold(word)
|
||||
end
|
||||
end)
|
||||
des = des:gsub("%*%*([%w$.]+)%*%*", italic("%1"))
|
||||
des = des:gsub("%*([%w$.]+)%*", bold("%1"))
|
||||
table.insert(functions, {
|
||||
proto = proto,
|
||||
returnProto = returnProto,
|
||||
des = des
|
||||
})
|
||||
else
|
||||
s, e = str:find("^%.%w+")
|
||||
if e then
|
||||
local cmd = str:sub(2, e)
|
||||
local txt = str:sub(e+1):gsub("^%s+", "")
|
||||
if cmd == "name" then
|
||||
name = name .. " - " .. txt
|
||||
elseif cmd == "desc" then
|
||||
txt = txt:gsub("%*%*([%w$.]+)%*%*", italic("%1"))
|
||||
txt = txt:gsub("%*([%w$.]+)%*", bold("%1"))
|
||||
table.insert(description, txt)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(functions, function(a, b)
|
||||
return a.proto < b.proto
|
||||
end)
|
||||
for f,func in pairs(functions) do
|
||||
func.proto = func.returnProto..func.proto
|
||||
end
|
||||
|
||||
tf:write(".TH thornWM 3\n.SH NAME\n"..name.."\n")
|
||||
|
||||
tf:write(".SH SYNOPSIS\n")
|
||||
for f,func in pairs(functions) do
|
||||
tf:write(".TP\n")
|
||||
tf:write(func.proto.."\n")
|
||||
end
|
||||
|
||||
tf:write(".SH DESCRIPTION\n")
|
||||
tf:write(table.concat(description, "\n").."\n")
|
||||
for f,func in pairs(functions) do
|
||||
tf:write(".TP\n"..func.proto.."\n")
|
||||
tf:write(func.des.."\n")
|
||||
end
|
||||
|
||||
tf:write(".SH COPYRIGHT\nThe contents of this Documentation are subject to the Public Documentation License Version 1.0\n")
|
||||
136
lib/async.lua
Normal file
136
lib/async.lua
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
-- This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ --
|
||||
--* .name Asynchronous callbacks for anything that needs it
|
||||
local C = require("c")
|
||||
local X = require("x")
|
||||
local Conf = require("conf")
|
||||
local ev = require("ev") -- https://github.com/brimworks/lua-ev
|
||||
local Async = {ev = ev}
|
||||
|
||||
--* fd(int fd, int events, function callback) Raw file descriptor callbacks
|
||||
function Async.fd(fd, evs, cb)
|
||||
ev.IO.new(cb, fd, evs):start(ev.Loop.default)
|
||||
end
|
||||
function Async.idle(cb)
|
||||
local id
|
||||
id = ev.Idle.new(function()
|
||||
id:stop(ev.Loop.default)
|
||||
cb()
|
||||
X.sync()
|
||||
end)
|
||||
id:start(ev.Loop.default)
|
||||
end
|
||||
|
||||
--* timer(float after, [float repeat], function callback) Calls **callback** after **after** seconds, and then repeats on a **repeat** second loop if it's provided
|
||||
function Async.timer(after, rep, cb)
|
||||
if not cb then rep, cb = cb, rep end
|
||||
if after <= 0 then after = 0.00000001 end
|
||||
ev.Timer.new(cb, after, rep):start(ev.Loop.default)
|
||||
end
|
||||
|
||||
--* .desc The *spawn** functions return a table with the **pid**, as well as *write*(*str* **data**) and *close*() for stdin
|
||||
local StdinWrapper = {
|
||||
write = function(self, text)
|
||||
C.write(self.stdin, text)
|
||||
return self
|
||||
end,
|
||||
close = function(self)
|
||||
C.close(self.stdin)
|
||||
end
|
||||
}
|
||||
StdinWrapper.__index = StdinWrapper
|
||||
--* table stdin spawn(str program, [str arg, ...], [function callback]) Spawns a program and calls **callback** once it exits. Parameters passed to **callback** are (*str* **stdout**, *str* **stderr**, *int* **exitStatus**)
|
||||
function Async.spawn(...)
|
||||
local args = {...}
|
||||
local cb
|
||||
if #args == 0 then error("Need a program") end
|
||||
if type(args[#args]) == "function" then cb = table.remove(args, #args) end
|
||||
local pid, stdin, stdout, stderr
|
||||
if cb then
|
||||
pid, stdin, stdout, stderr = C.spawn(args)
|
||||
if not pid then return nil, "Failed to spawn" end
|
||||
local childWatcher
|
||||
childWatcher = ev.Child.new(function(loop, child)
|
||||
childWatcher:stop(loop)
|
||||
local out = C.read(stdout)
|
||||
local err = C.read(stderr)
|
||||
C.close(stdin, stdout, stderr)
|
||||
cb(out, err, child:getstatus().exit_status)
|
||||
end, pid, false)
|
||||
childWatcher:start(ev.Loop.default)
|
||||
else
|
||||
pid, stdin, stdout, stderr = C.spawn(args)
|
||||
if not pid then return nil, "Failed to spawn" end
|
||||
end
|
||||
return setmetatable({ pid = pid, stdin = stdin }, StdinWrapper)
|
||||
end
|
||||
--* table stdin spawnShell(str command, [str shell], [function callback]) Runs **command** in a shell instace. The shell is the first of **shell**, **Conf.shell**, **$SHELL**, "/bin/sh". Parameters passed to **callback** are the same as with *spawn*
|
||||
function Async.spawnShell(str, shell, cb)
|
||||
if not cb then cb, shell = shell, cb end
|
||||
return Async.spawn(shell or Conf.shell or os.getenv("SHELL") or "/bin/sh", "-c", str, cb)
|
||||
end
|
||||
--* table stdin spawnRead(str program, [str arg, ...], function dataCallback, [function exitCallback]) Spawns a program and calls **dataCallback** when the program writes to stdout or stderr, then **exitCallback** once it exits. Parameters to **dataCallback** are (*str* **stdout**, *str* **stderr**). Paramaters to **exitCallback** are (*int* **exitStatus**)
|
||||
function Async.spawnRead(...)
|
||||
local args = {...}
|
||||
if #args < 2 then error("Need a program and callback") end
|
||||
local outcb = table.remove(args, #args)
|
||||
if type(outcb) ~= "function" then error("Need a callback") end
|
||||
local exitcb
|
||||
if type(args[#args]) == "function" then
|
||||
if #args < 2 then error("Need a program") end
|
||||
outcb, exitcb = table.remove(args, #args), outcb
|
||||
if type(outcb) ~= "function" then error("Need a callback") end
|
||||
end
|
||||
local pid, stdin, stdout, stderr = C.spawn(args)
|
||||
C.setNonBlocking(stdout)
|
||||
C.setNonBlocking(stderr)
|
||||
local stdoutWatcher = ev.IO.new(function(loop, io, revents)
|
||||
local d = C.read(stdout)
|
||||
print(d, #d)
|
||||
if d and #d > 0 then
|
||||
outcb(d, nil)
|
||||
end
|
||||
end, stdout, ev.READ)
|
||||
stdoutWatcher:start(ev.Loop.default)
|
||||
local exitWatcher
|
||||
exitWatcher = ev.Child.new(function(loop, child, revents)
|
||||
local out = C.read(stdout)
|
||||
local err = C.read(stderr)
|
||||
C.close(stdout, stderr, stdin)
|
||||
stdoutWatcher:stop(loop)
|
||||
exitWatcher:stop(loop)
|
||||
outcb(out, err)
|
||||
if exitcb then exitcb(child:getstatus().exit_status) end
|
||||
end, pid, false)
|
||||
exitWatcher:start(ev.Loop.default)
|
||||
return setmetatable({pid = pid, stdin = stdin}, StdinWrapper)
|
||||
end
|
||||
--* table stdin spawnReadShell(str command, [str shell], function dataCallback, [function exitCallback]) Combination of *spawnRead* and *spawnShell*
|
||||
function Async.spawnReadShell(str, shell, outcb, exitcb)
|
||||
if type(shell) == "function" then
|
||||
shell, outcb, exitcb = nil, shell, outcb
|
||||
end
|
||||
return Async.spawnRead(shell or Conf.shell or os.getenv("SHELL") or "/bin/sh", "-c", str, outcb, exitcb)
|
||||
end
|
||||
function Async.readFile(path, cb)
|
||||
local stdout = C.getFD(assert(io.open(path, "r")))
|
||||
C.setNonBlocking(stdout)
|
||||
local chunks = {}
|
||||
local stdoutWatcher
|
||||
stdoutWatcher = ev.IO.new(function()
|
||||
local c = C.read(stdout)
|
||||
if c and #c > 0 then
|
||||
table.insert(chunks, c)
|
||||
else
|
||||
C.close(stdout)
|
||||
stdoutWatcher:stop(ev.Loop.default)
|
||||
cb(table.concat(chunks))
|
||||
end
|
||||
end, stdout, ev.READ)
|
||||
stdoutWatcher:start(ev.Loop.default)
|
||||
end
|
||||
|
||||
function Async.loop()
|
||||
ev.Loop.default:loop()
|
||||
end
|
||||
|
||||
return Async
|
||||
46
lib/conf.lua
Normal file
46
lib/conf.lua
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
-- This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ --
|
||||
-- (re)loads the config file and stores settings
|
||||
local X = require("x")
|
||||
local Conf = {}
|
||||
|
||||
function Conf.onKeyPress(key, mod, cb)
|
||||
if type(key) ~= "string" or type(mod) ~= "number" or type(cb) ~= "function" then
|
||||
error("Parameters are wrong")
|
||||
end
|
||||
local n, err = X.grabKey(key, mod)
|
||||
assert(not err, err)
|
||||
local id = key..mod
|
||||
if Conf.keyPressHandlers[id] then
|
||||
error(string.format("Key %s with mod %i already bound", key, mod))
|
||||
end
|
||||
Conf.keyPressHandlers[id] = cb
|
||||
end
|
||||
|
||||
function Conf.on(t, cb)
|
||||
Conf.eventHandlers[t] = cb
|
||||
end
|
||||
|
||||
function Conf.init(path)
|
||||
Conf.keyPressHandlers = {}
|
||||
Conf.keyReleaseHandlers = {}
|
||||
Conf.eventHandlers = {}
|
||||
path = path or Conf.cmdArgs.config or (os.getenv("XDG_CONFIG_HOME") or (os.getenv("HOME").."/.config")).."/thornWM/config.lua"
|
||||
local func = loadfile(path)
|
||||
if func then
|
||||
local s, err = pcall(func)
|
||||
if s then
|
||||
return
|
||||
end
|
||||
print(string.format("Config %s failed:%s", path, err))
|
||||
os.exit(1)
|
||||
else
|
||||
print(string.format("Config %s not found", path))
|
||||
end
|
||||
local s, err = pcall((os.getenv("XDG_CONFIG_DIRS") or "/etc/xdg") .. "thornWM/config.lua")
|
||||
if not s then
|
||||
print(string.format("Config %s failed:%s", path, err))
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
return Conf
|
||||
40
lib/event.lua
Normal file
40
lib/event.lua
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
-- This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ --
|
||||
-- Event handling
|
||||
local X = require("x")
|
||||
local Conf = require("conf")
|
||||
local Tree = require("tree")
|
||||
local Event = {}
|
||||
|
||||
local xEventHandlers = {}
|
||||
xEventHandlers[X.MapRequest] = function(ev)
|
||||
if Tree.windows[ev.id] then return print("got duplicate map request") end
|
||||
local win = Tree.newWindow(ev)
|
||||
local handler = Conf.eventHandlers.newWindow
|
||||
if handler and win then handler(win) end
|
||||
end
|
||||
xEventHandlers[X.DestroyNotify] = function(ev)
|
||||
local win = Tree.windows[ev.id]
|
||||
local handler = Conf.eventHandlers.delWindow
|
||||
if handler and win then handler(win) end
|
||||
if win then
|
||||
win:unmanage()
|
||||
end
|
||||
end
|
||||
xEventHandlers[X.KeyPress] = function(ev)
|
||||
local handler = Conf.keyPressHandlers[ev.key..ev.mask]
|
||||
if handler then handler(ev) end
|
||||
end
|
||||
|
||||
function Event.handleXEvents()
|
||||
while X.pending() > 0 do
|
||||
local ev = X.nextEvent()
|
||||
if xEventHandlers[ev.type] then
|
||||
xEventHandlers[ev.type](ev)
|
||||
end
|
||||
if Conf.eventHandlers[ev.type] then
|
||||
pcall(Conf.eventHandlers[ev.type], ev)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Event
|
||||
61
lib/ewmh.lua
Normal file
61
lib/ewmh.lua
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
-- This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ --
|
||||
--* Handles Extended Window Manager Hint (https://specifications.freedesktop.org/wm-spec/latest/) stuff that doesn't really fit in Tree
|
||||
local Tree
|
||||
local X = require("x")
|
||||
local EWMH = {}
|
||||
|
||||
local pid = tonumber(io.open("/proc/self/stat"):read(8):match("%d+"))
|
||||
|
||||
--* init(str name) Sets up properties to the root and support check windows
|
||||
function EWMH.init(name)
|
||||
Tree = require("tree")
|
||||
local supported = {
|
||||
X.getAtoms(
|
||||
"_NET_SUPPORTED", "_NET_SUPPORTING_WM_CHECK", "_NET_WM_NAME", "_NET_WM_PID", "_NET_WM_WINDOW_TYPE",
|
||||
"_NET_WM_WINDOW_TYPE_DESKTOP", "_NET_WM_WINDOW_TYPE_DOCK", "_NET_WM_WINDOW_TYPE_TOOLBAR", "_NET_WM_WINDOW_TYPE_MENU",
|
||||
"_NET_WM_WINDOW_TYPE_UTILITY", "_NET_WM_WINDOW_TYPE_SPLASH", "_NET_WM_WINDOW_TYPE_DIALOG", "_NET_WM_WINDOW_TYPE_NORMAL"
|
||||
)
|
||||
}
|
||||
local supportWin = X.createWindow()
|
||||
X.setProperty(supportWin, X.getAtoms("WM_CLASS"), X.getAtoms("STRING"), 8, X.PropModeReplace, { name, name })
|
||||
X.setProperty(supportWin, X.getAtoms("_NET_WM_NAME"), X.getAtoms("UTF8_STRING"), 8, X.PropModeReplace, name)
|
||||
X.setProperty(supportWin, X.getAtoms("_NET_SUPPORTING_WM_CHECK"), X.getAtoms("WINDOW"), 32, X.PropModeReplace, supportWin)
|
||||
X.setProperty(supportWin, X.getAtoms("_NET_WM_PID"), X.getAtoms("CARDINAL"), 32, X.PropModeReplace, pid)
|
||||
X.setProperty(X.root, X.getAtoms("_NET_SUPPORTING_WM_CHECK"), X.getAtoms("WINDOW"), 32, X.PropModeReplace, supportWin)
|
||||
X.setProperty(X.root, X.getAtoms("_NET_SUPPORTED"), X.getAtoms("ATOM"), 32, X.PropModeReplace, supported)
|
||||
end
|
||||
|
||||
--* bool should shouldManage(table win) Returns a boolean on whether the WM should manage the window
|
||||
function EWMH.shouldManage(win)
|
||||
local winTypeAtom = X.getProperty(win.id, X.getAtoms("_NET_WM_WINDOW_TYPE"), 0, X.getAtoms("ATOM"))
|
||||
if not winTypeAtom then return true end
|
||||
local winType = X.getAtomNames(winTypeAtom)
|
||||
if winType == "_NET_WM_WINDOW_TYPE_DOCK" then
|
||||
local attrs = X.getWindowAttributes(win.id)
|
||||
win.winType = "dock"
|
||||
if attrs.y == 0 then
|
||||
-- print(inspect(Tree))
|
||||
local m = Tree.focusedMonitor.margins
|
||||
-- print("HHIHIHII", inspect(m))
|
||||
-- print("m", inspect(attrs))
|
||||
Tree.focusedMonitor.dockMargins = { top = attrs.height, bottom = 0, left = 0, right = 0 }
|
||||
Tree.focusedMonitor:setMargins(Tree.focusedMonitor.margins)
|
||||
end
|
||||
return false
|
||||
elseif winType == "_NET_WM_WINDOW_TYPE_SPLASH" then
|
||||
return false
|
||||
else
|
||||
win.winType = winType or "normal"
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--* bool should shouldTile(table win) Returns a boolean on whether the WM should tile the window by default
|
||||
function EWMH.shouldTile(win)
|
||||
if win.winType == "_NET_WM_WINDOW_TYPE_DIALOG" then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return EWMH
|
||||
85
lib/tree/init.lua
Normal file
85
lib/tree/init.lua
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
-- This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ --
|
||||
--* .name Manages the tree structure that holds all monitors, containers, windows, and tags
|
||||
local X = require("x")
|
||||
local EWMH = require("ewmh")
|
||||
local Conf = require("conf")
|
||||
local Tree = {
|
||||
windows = {},
|
||||
monitors = {},
|
||||
focusedWindow = nil,
|
||||
focusedMonitor = nil,
|
||||
-- tags = {},
|
||||
}
|
||||
|
||||
-- local Tag = require("tree.tag")
|
||||
local Window = require("tree.window")
|
||||
|
||||
function Tree.newWindow(ev)
|
||||
local win = Window.new(ev.id)
|
||||
if EWMH.shouldManage(win) then
|
||||
Tree.windows[win.id] = win
|
||||
win.managed = true
|
||||
if EWMH.shouldTile(win) then
|
||||
table.insert(Tree.focusedMonitor.stack, win)
|
||||
win.monitor = Tree.focusedMonitor
|
||||
-- Tree.focusedMonitor:updateTiling()
|
||||
else
|
||||
win:float()
|
||||
end
|
||||
win:show()
|
||||
else
|
||||
win.managed = false
|
||||
win:show()
|
||||
return
|
||||
end
|
||||
return win
|
||||
end
|
||||
|
||||
----* table tag Tree.newTag(any id, [table preset]) Creates a new tag that can then be added to tables. New tags are also initialised automatically with *Window*:*addTag*()
|
||||
--function Tree.newTag(id, tbl)
|
||||
-- if Tree.tags[id] then return Tree.tags[id] end
|
||||
-- local tag = Tag.new(tbl)
|
||||
-- tag.id = id
|
||||
-- Tree.tags[id] = tag
|
||||
-- return tag
|
||||
--end
|
||||
--* Tree.init() Sets up the tree
|
||||
function Tree.init()
|
||||
Window.init()
|
||||
for m,monitor in pairs(X.getMonitors()) do
|
||||
Tree.monitors[m] = monitor
|
||||
monitor.number = m
|
||||
monitor.stack = {}
|
||||
monitor.dockMargins = { top = 0, bottom = 0, left = 0, right = 0 }
|
||||
monitor.margins = { top = 5, bottom = 5, left = 5, right = 5 }
|
||||
function monitor:updateTiling()
|
||||
if self.tiler then
|
||||
self.tiler:tile(self.stack, self)
|
||||
end
|
||||
end
|
||||
function monitor:setMargins(m)
|
||||
print("привет", inspect(m))
|
||||
self.margins.top = m.top + self.dockMargins.top
|
||||
self.margins.bottom = m.bottom + self.dockMargins.bottom
|
||||
self.margins.right = m.right + self.dockMargins.right
|
||||
self.margins.left = m.left + self.dockMargins.left
|
||||
end
|
||||
if Conf.eventHandlers.newMonitor then
|
||||
Conf.eventHandlers.newMonitor(monitor)
|
||||
end
|
||||
end
|
||||
Tree.focusedMonitor = Tree.monitors[1]
|
||||
-- Tree.newTag("focused")
|
||||
end
|
||||
-- function Tree.updateDirty()
|
||||
-- if Tree.dirtyGeometry then
|
||||
-- for w,win in pairs(Tree.windows) do
|
||||
-- if win.dirtyGeometry then
|
||||
-- win:applyGeometry()
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
return Tree
|
||||
|
||||
56
lib/tree/tag.lua
Normal file
56
lib/tree/tag.lua
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
---- TAG --
|
||||
--local Tag = {}
|
||||
--Tag.__index = Tag
|
||||
--function Tag.new(tag)
|
||||
-- tag = tag or {}
|
||||
-- tag.windows = {}
|
||||
-- return setmetatable(tag, Tag)
|
||||
--end
|
||||
----* Tag:setBorderColour(int colour) Sets the tag's window's border colour. **colour** is in the form of 0xRRGGBB
|
||||
--function Tag:setBorderColour(col)
|
||||
-- self.borderColour = col
|
||||
-- for w,win in pairs(self.windows) do win:updateTag(self) end
|
||||
--end
|
||||
--function Tag:setBorderWidth(width)
|
||||
-- self.borderWidth = width
|
||||
-- for w,win in pairs(self.windows) do win:updateTag(self) end
|
||||
--end
|
||||
--function Tag:setMargin(top, right, bottom, left)
|
||||
-- if type(top) == "table" then
|
||||
-- self.margin = top
|
||||
-- else
|
||||
-- self.margin = {top = top, right = right, bottom = bottom, left = left}
|
||||
-- end
|
||||
-- for w,win in pairs(self.windows) do win:updateTag(self) end
|
||||
--end
|
||||
----* Tag:set(table props) Sets a table worth of properties
|
||||
--function Tag:set(tbl)
|
||||
-- for prop,value in pairs(tbl) do
|
||||
-- if prop == "borderColour" then
|
||||
-- self:setBorderColour(value)
|
||||
-- elseif prop == "borderWidth" then
|
||||
-- self:setBorderWidth(value)
|
||||
-- elseif prop == "visible" then
|
||||
-- if value then self:show() else self:hide() end
|
||||
-- elseif prop == "margin" then
|
||||
-- self:setMargin(value)
|
||||
-- end
|
||||
-- end
|
||||
--end
|
||||
----* Tag:show() Unhides windows from the screen
|
||||
--function Tag:show()
|
||||
-- self.visible = true
|
||||
-- for w,win in pairs(self.windows) do
|
||||
-- win:updateTag(self)
|
||||
-- end
|
||||
--end
|
||||
----* Tag:hide() Hides windows from the screen
|
||||
--function Tag:hide()
|
||||
-- self.visible = false
|
||||
-- for w,win in pairs(self.windows) do
|
||||
-- win:updateTag(self)
|
||||
-- end
|
||||
--end
|
||||
|
||||
--return Tag
|
||||
|
||||
91
lib/tree/tilers.lua
Normal file
91
lib/tree/tilers.lua
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
local Tilers = {}
|
||||
local inspect = require("inspect")
|
||||
|
||||
local dirs = {
|
||||
left = function(m, w, h, stack, r, gap)
|
||||
local masterBase = {
|
||||
w = w * r - gap/2,
|
||||
h = h,
|
||||
x = m.left,
|
||||
y = m.top
|
||||
}
|
||||
local childBase = {
|
||||
w = (w * (1-r)) - gap/2,
|
||||
h = (masterBase.h / (#stack-1)) - gap + gap / (#stack-1),
|
||||
x = masterBase.x + masterBase.w + gap,
|
||||
y = m.top
|
||||
}
|
||||
local childOffset = {
|
||||
x = 0, y = childBase.h + gap
|
||||
}
|
||||
return masterBase, childBase, childOffset
|
||||
end,
|
||||
top = function(m, w, h, stack, r, gap)
|
||||
local masterBase = {
|
||||
w = w,
|
||||
h = h * r - gap,
|
||||
x = m.left,
|
||||
y = m.top
|
||||
}
|
||||
local childBase = {
|
||||
w = (masterBase.w / (#stack-1)) - gap + gap / (#stack-1),
|
||||
h = (h * (1-r)) - gap,
|
||||
x = m.left,
|
||||
y = masterBase.y + masterBase.h + gap,
|
||||
}
|
||||
local childOffset = {
|
||||
x = childBase.w + gap, y = 0
|
||||
}
|
||||
return masterBase, childBase, childOffset
|
||||
end
|
||||
}
|
||||
dirs.right = function(...)
|
||||
local masterBase, childBase, childOffset = dirs.left(...)
|
||||
masterBase.x, childBase.x = childBase.x, masterBase.x
|
||||
masterBase.w, childBase.w = childBase.w, masterBase.w
|
||||
return masterBase, childBase, childOffset
|
||||
end
|
||||
dirs.bottom = function(...)
|
||||
local masterBase, childBase, childOffset = dirs.top(...)
|
||||
masterBase.y, childBase.y = childBase.y, masterBase.y
|
||||
masterBase.h, childBase.h = childBase.h, masterBase.h
|
||||
return masterBase, childBase, childOffset
|
||||
end
|
||||
|
||||
local function makeTiler(getOff)
|
||||
return {
|
||||
r = 0.5,
|
||||
tile = function(self, stack, monitor)
|
||||
local m = monitor.margins
|
||||
local w = monitor.width - m.left - m.right
|
||||
local h = monitor.height - m.top - m.bottom
|
||||
if #stack == 1 then
|
||||
stack[1]:setGeometry(m.left, m.top, w, h)
|
||||
elseif #stack >= 2 then
|
||||
local masterBase, childBase, childOffset = getOff(m, w, h, stack, self.r, self.gap)
|
||||
stack[1]:setGeometry(masterBase.x, masterBase.y, masterBase.w, masterBase.h)
|
||||
for i=2,#stack do
|
||||
stack[i]:setGeometry(childBase.x, childBase.y, childBase.w, childBase.h)
|
||||
childBase.x = childBase.x + childOffset.x
|
||||
childBase.y = childBase.y + childOffset.y
|
||||
end
|
||||
end
|
||||
end,
|
||||
setGap = function(self, gap)
|
||||
assert(type(gap) == "number")
|
||||
self.gap = gap
|
||||
end,
|
||||
gap = 0
|
||||
}
|
||||
end
|
||||
|
||||
Tilers = {
|
||||
masterTop = makeTiler(dirs.top),
|
||||
masterLeft = makeTiler(dirs.left),
|
||||
masterRight = makeTiler(dirs.right),
|
||||
masterBottom = makeTiler(dirs.bottom),
|
||||
}
|
||||
|
||||
return Tilers
|
||||
|
||||
|
||||
122
lib/tree/window.lua
Normal file
122
lib/tree/window.lua
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
local Tree
|
||||
local X = require("x")
|
||||
local Util = require("util")
|
||||
local Async = require("async")
|
||||
local Tag = require("tree.tag")
|
||||
|
||||
local function round(n)
|
||||
return math.floor(n+0.5)
|
||||
end
|
||||
|
||||
local Window = {
|
||||
x = 0, y = 0,
|
||||
w = 0, h = 0,
|
||||
bx = 0, by = 0,
|
||||
bw = 0, bh = 0,
|
||||
margin = {top = 0, right = 0, bottom = 0, left = 0},
|
||||
borderWidth = 0
|
||||
}
|
||||
Window.__index = Window
|
||||
function Window.new(id)
|
||||
local window = {
|
||||
id = id,
|
||||
monitor = Tree.focusedMonitor,
|
||||
-- tags = {
|
||||
-- main = Tag.new()
|
||||
-- }
|
||||
}
|
||||
setmetatable(window, Window)
|
||||
return window
|
||||
end
|
||||
function Window:getClass()
|
||||
if self.class == nil then
|
||||
local c1, c2 = X.getProperty(self.id, X.getAtoms("WM_CLASS"), 0, X.AnyPropertyType)
|
||||
if c1 then
|
||||
self.class = {c1, c2}
|
||||
else
|
||||
self.class = false
|
||||
end
|
||||
end
|
||||
return self.class
|
||||
end
|
||||
function Window:getName()
|
||||
self.name = X.getProperty(self.id, X.getAtoms("_NET_WM_NAME"), 0, X.getAtoms("UTF8_STRING"))
|
||||
if not self.name then
|
||||
self.name = X.getProperty(self.id, X.getAtoms("WM_NAME"), 0, X.AnyPropertyType)
|
||||
end
|
||||
return self.name
|
||||
end
|
||||
function Window:float()
|
||||
end
|
||||
function Window:setGeometry(x, y, w, h)
|
||||
if self.x ~= x or self.y ~= y or self.width ~= w or self.h ~= h then
|
||||
self.x, self.y, self.width, self.height = x, y, w, h
|
||||
self:applyGeometry()
|
||||
end
|
||||
-- self.monitor.dirtyGeometry = true
|
||||
-- self.dirtyGeometry = true
|
||||
-- Tree.dirtyGeometry = true
|
||||
-- self.dirtyGeometry = true
|
||||
-- print("set")
|
||||
-- Async.idle(function()
|
||||
-- if self.dirtyGeometry then
|
||||
-- self:applyGeometry()
|
||||
-- end
|
||||
-- print("ran")
|
||||
-- self.dirtyGeometry = false
|
||||
-- end)
|
||||
end
|
||||
function Window:applyGeometry()
|
||||
X.setWindowGeometry(self.id, self.x, self.y, self.width, self.height)
|
||||
end
|
||||
function Window:show()
|
||||
self.visible = true
|
||||
X.mapWindow(self.id)
|
||||
Tree.focusedMonitor:updateTiling()
|
||||
end
|
||||
function Window:hide()
|
||||
self.visible = false
|
||||
X.unmapWindow(self.id)
|
||||
Tree.focusedMonitor:updateTiling()
|
||||
end
|
||||
--* Window:focus() Moves keyboard focus to window, appends it to the focus history (**Tree**.**focusHistory**), and adds the "focused" tag
|
||||
function Window:unmanage()
|
||||
local i = self:getIndex()
|
||||
table.remove(self.monitor.stack, i)
|
||||
if self.focused then
|
||||
Tree.focusedWindow = nil
|
||||
self.focused = nil
|
||||
end
|
||||
Tree.windows[self.id] = nil
|
||||
Tree.focusedMonitor:updateTiling()
|
||||
end
|
||||
function Window:getIndex()
|
||||
return Util.indexOf(self.monitor.stack, self)
|
||||
end
|
||||
function Window:getNext(d)
|
||||
d = d or 1
|
||||
local stack = Tree.focusedMonitor.stack
|
||||
local w = self:getIndex()
|
||||
local i = (((w-1) + d) % #stack) + 1
|
||||
return stack[i]
|
||||
end
|
||||
function Window:focus()
|
||||
self.focused = true
|
||||
if Tree.focusedWindow then
|
||||
Tree.focusedWindow.focused = false
|
||||
end
|
||||
Tree.focusedWindow = self
|
||||
Tree.focusedContainer = self.parent
|
||||
Tree.focusedMonitor.focusedContainer = self.parent
|
||||
X.setInputFocus(self.id)
|
||||
end
|
||||
function Window:getStackIndex()
|
||||
return Util.indexOf(self, self.monitor.stack)
|
||||
end
|
||||
|
||||
function Window.init()
|
||||
Tree = require("tree")
|
||||
end
|
||||
|
||||
return Window
|
||||
|
||||
17
lib/util.lua
Normal file
17
lib/util.lua
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
local Util = {}
|
||||
|
||||
function Util.indexOf(haystack, needle)
|
||||
for b,blade in pairs(haystack) do
|
||||
if blade == needle then return b end
|
||||
end
|
||||
end
|
||||
|
||||
function Util.getNext(stack, item, d)
|
||||
local w = Util.indexOf(stack, item)
|
||||
if not w then return nil end
|
||||
local i = (((w-1) + d) % #stack) + 1
|
||||
return stack[i]
|
||||
end
|
||||
|
||||
return Util
|
||||
|
||||
86
makefile
Normal file
86
makefile
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
CC = cc
|
||||
CFLAGS = -Wall -pipe -Og -g -march=native
|
||||
|
||||
USEXRANDR = 1
|
||||
SHAREPATH = /usr/local/share/thornWM
|
||||
DOCPATH = /usr/local/share/man
|
||||
PROGPATH = /usr/local/bin
|
||||
CONFPATH = /etc/xdg/thornWM
|
||||
|
||||
ifeq ($(USEXRANDR), 1)
|
||||
XFLAGS+=-lXrandr
|
||||
CFLAGS+=-DL_XRANDR
|
||||
else
|
||||
XFLAGS+=-lXinerama
|
||||
endif
|
||||
|
||||
LUA_VERSION = 5.1
|
||||
ifeq ($(LUA_VERSION), jit)
|
||||
CFLAGS += `pkg-config --cflags luajit || echo -I/usr/include/lua{,5.1}`
|
||||
else ifeq ($(LUA_VERSION), 5.1)
|
||||
CFLAGS += `pkg-config --cflags lua5.1 --silence-errors`
|
||||
else ifeq ($(LUA_VERSION), 5.2)
|
||||
CFLAGS += `pkg-config --cflags lua5.2 --silence-errors`
|
||||
else ifeq ($(LUA_VERSION), 5.3)
|
||||
CFLAGS += `pkg-config --cflags lua5.3 --silence-errors`
|
||||
else ifeq ($(LUA_VERSION), 5.4)
|
||||
CFLAGS += `pkg-config --cflags lua5.4 --silence-errors`
|
||||
else
|
||||
$(error Invalid Lua version $(LUA_VERSION))
|
||||
endif
|
||||
|
||||
all: libs docs
|
||||
libs: lib/x.so lib/c.so
|
||||
docs: docs/thornWM.x.3 docs/thornWM.c.3 docs/thornWM.async.3 docs/thornWM.ewmh.3 docs/thornWM.tree.3
|
||||
install: installProgram installLibs installDocs
|
||||
uninstall: uninstallProgram uninstallLibs uninstallDocs
|
||||
.PHONY: all libs docs install uninstall installProgram installLibs installDocs uninstallProgram uninstallLibs uninstallDocs
|
||||
|
||||
|
||||
# PROGRAM
|
||||
installProgram:
|
||||
mkdir -p $(PROGPATH) $(CONFPATH)
|
||||
cp thornWM $(PROGPATH)/
|
||||
cp config.lua $(CONFPATH)/
|
||||
uninstallProgram:
|
||||
rm -f $(PROGPATH)/thornWM $(CONFPATH)/config.lua
|
||||
rmdir --ignore-fail-on-non-empty $(PROGPATH) $(CONFPATH)
|
||||
|
||||
# LIBS
|
||||
lib/x.so: src/x.c src/common.h makefile
|
||||
@echo $(CC) $< $@
|
||||
@$(CC) $< -o $@ -lX11 $(CFLAGS) -shared -fPIC $(XFLAGS)
|
||||
lib/c.so: src/c.c src/common.h makefile
|
||||
@echo $(CC) $< $@
|
||||
@$(CC) $< -o $@ $(CFLAGS) -shared -fPIC
|
||||
|
||||
installLibs: libs
|
||||
mkdir -p $(SHAREPATH)/lib
|
||||
cp lib/* $(SHAREPATH)/lib/
|
||||
uninstallLibs:
|
||||
rm -f $(SHAREPATH)/*.lua $(SHAREPATH)/*.so
|
||||
rmdir --ignore-fail-on-non-empty $(SHAREPATH) $(SHAREPATH)/lib
|
||||
|
||||
|
||||
# DOCS
|
||||
docs/thornWM.%.3: src/%.c docs/genDocs
|
||||
docs/genDocs $< $@
|
||||
docs/thornWM.%.3: lib/%.lua docs/genDocs
|
||||
docs/genDocs $< $@
|
||||
docs/thornWM.%.3: lib/%/init.lua docs/genDocs
|
||||
docs/genDocs $< $@
|
||||
|
||||
installDocs: docs
|
||||
mkdir -p $(DOCPATH)/man{1,3}
|
||||
cp docs/thornWM.1 $(DOCPATH)/man1/
|
||||
cp docs/*.3 $(DOCPATH)/man3/
|
||||
uninstallDocs:
|
||||
rm -f $(DOCPATH)/man3/thornWM.x.3 $(DOCPATH)/man3/thornWM.async.3 $(DOCPATH)/man3/thornWM.c.3 \
|
||||
$(DOCPATH)/man3/thornWM.ewmh.3 $(DOCPATH)/man3/thornWM.tree.3 $(DOCPATH)/man1/thornWM.1
|
||||
rmdir --ignore-fail-on-non-empty $(DOCPATH)
|
||||
|
||||
|
||||
clean:
|
||||
rm -fv -- lib/*.so docs/*.3
|
||||
|
||||
.PHONY: all libs docs intsall installProgram installDocs installLibs clean
|
||||
BIN
mynd.png.webp
Normal file
BIN
mynd.png.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 724 KiB |
25
scripts/test-x.lua
Executable file
25
scripts/test-x.lua
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/env luajit
|
||||
package.cpath = package.cpath .. ";lib/?.so"
|
||||
local X = require("x")
|
||||
|
||||
local root = assert(X.open())
|
||||
local win = X.createWindow()
|
||||
|
||||
X.setErrorHandler(print)
|
||||
|
||||
atom, integer = X.getAtoms("ATOM", "INTEGER")
|
||||
assert(atom == 4 and integer == 19)
|
||||
assert(X.getAtomNames(atom) == "ATOM")
|
||||
|
||||
X.setProperty(win, X.getAtoms("test"), atom, 32, X.PropModeReplace, 69)
|
||||
assert(X.getProperty(win, X.getAtoms("test"), 0, atom) == 69)
|
||||
|
||||
X.setProperty(win, X.getAtoms("test"), X.getAtoms("STRING"), 8, X.PropModeReplace, "hello")
|
||||
assert(X.getProperty(win, X.getAtoms("test"), 0, X.getAtoms("STRING")) == "hello")
|
||||
|
||||
X.delProperty(win, X.getAtoms("test"))
|
||||
assert(not X.delProperty(win, X.getAtoms("test"), 0, X.getAtoms("STRING")))
|
||||
|
||||
assert(X.getMonitors() and X.getWindowAttributes(win))
|
||||
|
||||
X.close()
|
||||
18
scripts/xephyr.sh
Executable file
18
scripts/xephyr.sh
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
#!/bin/env sh
|
||||
|
||||
# test/xephyr.sh ./thornWM -c config.lua
|
||||
# test/xephyr.sh test/x.lua
|
||||
|
||||
Xephyr -br -ac -noreset -screen 1920x1080 :1 &
|
||||
sleep 0.1
|
||||
export DISPLAY=:1
|
||||
|
||||
export LUA_PATH="/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua;/usr/lib/lua/5.1/?.lua;/usr/lib/lua/5.1/?/init.lua;./?.lua;./?/init.lua;/root/.luarocks/share/lua/5.1/?.lua;/root/.luarocks/share/lua/5.1/?/init.lua"
|
||||
export LUA_CPATH="/usr/lib/lua/5.1/?.so;/usr/lib/lua/5.1/loadall.so;./?.so;/root/.luarocks/lib/lua/5.1/?.so"
|
||||
"$@" ; kill %1 &>/dev/null
|
||||
|
||||
# mkfifo /tmp/thornWM.fifo
|
||||
# (while true; do cat /tmp/thornWM.fifo; done) | th ./thornWM -c config.lua ; kill %1
|
||||
|
||||
wait
|
||||
|
||||
145
src/c.c
Normal file
145
src/c.c
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/* This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ */
|
||||
//* .name Glorified UNIX function exporter
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
#include "common.h"
|
||||
|
||||
typedef struct myluaL_Stream {
|
||||
FILE *f;
|
||||
lua_CFunction closef;
|
||||
} myluaL_Stream;
|
||||
|
||||
//* int fd getFD(file file) Returns the file descriptor associated with a Lua file
|
||||
static int l_getFD(lua_State* L){
|
||||
lua_pushinteger(L, fileno(((myluaL_Stream*)luaL_checkudata(L, 1, "FILE*"))->f));
|
||||
return 1;
|
||||
}
|
||||
|
||||
//* int pid, int stdin, int stdout, int stderr spawn(str program, [str arg, ...]) Spawns an external program from the system path and pipes its output
|
||||
static int l_spawn(lua_State* L){
|
||||
#define R 0
|
||||
#define W 1
|
||||
if(lua_type(L, 1) != LUA_TTABLE)
|
||||
luaL_argerror(L, 1, "Need a table");
|
||||
int nargs = lua_objlen(L, 1);
|
||||
if(nargs < 1)
|
||||
luaL_argerror(L, 1, "Need a table with something in it");
|
||||
char* args[nargs+1];
|
||||
args[nargs] = NULL;
|
||||
for(int i = 0; i < nargs; i++){
|
||||
lua_rawgeti(L, 1, i+1);
|
||||
args[i] = (char*)luaL_checkstring(L, -1);
|
||||
}
|
||||
int pid;
|
||||
int pin[2];
|
||||
int pout[2];
|
||||
int perr[2];
|
||||
if(pipe(pin) < 0 || pipe(pout) < 0 || pipe(perr) < 0)
|
||||
L_RETURN_ERROR("Failed to create pipes");
|
||||
if((pid = fork())){
|
||||
// parent
|
||||
if(pid < 0)
|
||||
L_RETURN_ERROR("Failed to fork");
|
||||
close(pin[R]);
|
||||
close(pout[W]);
|
||||
close(perr[W]);
|
||||
lua_pushinteger(L, pid);
|
||||
lua_pushinteger(L, pin[W]);
|
||||
lua_pushinteger(L, pout[R]);
|
||||
lua_pushinteger(L, perr[R]);
|
||||
return 4;
|
||||
}
|
||||
// child
|
||||
close(pin[W]);
|
||||
close(pout[R]);
|
||||
close(perr[R]);
|
||||
dup2(pin[R], STDIN_FILENO);
|
||||
dup2(pout[W], STDOUT_FILENO);
|
||||
dup2(perr[W], STDERR_FILENO);
|
||||
execvp(args[0], args); // should never return
|
||||
int en = errno;
|
||||
dprintf(STDERR_FILENO, "thornWM: Failed to run '%s': %s", args[0], strerror(en));
|
||||
exit(en);
|
||||
return 0;
|
||||
#undef R
|
||||
#undef W
|
||||
}
|
||||
|
||||
//* str data read(int fd) Reads as much as it can from a file descriptor
|
||||
static int l_read(lua_State* L){
|
||||
int fd = luaL_checkinteger(L, 1);
|
||||
if(fcntl(fd, F_GETFD) < 0)
|
||||
L_RETURN_ERROR("Invalid FD");
|
||||
luaL_Buffer buf;
|
||||
luaL_buffinit(L, &buf);
|
||||
int r;
|
||||
char readBuf[4096];
|
||||
while(1){
|
||||
r = read(fd, readBuf, 4096);
|
||||
if(r <= 0)
|
||||
break;
|
||||
luaL_addlstring(&buf, readBuf, r);
|
||||
}
|
||||
luaL_pushresult(&buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
//* close(int fd) Closes a file descriptor
|
||||
static int l_close(lua_State* L){
|
||||
int top = lua_gettop(L);
|
||||
for(int i = 1; i <= top; i++){
|
||||
int fd = luaL_checkinteger(L, i);
|
||||
if(fcntl(fd, F_GETFD) < 0)
|
||||
L_RETURN_ERROR("Invalid FD");
|
||||
close(fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//* write(int fd, str data) Writes **data** to a file descriptor
|
||||
static int l_write(lua_State* L){
|
||||
int fd = luaL_checkinteger(L, 1);
|
||||
size_t len;
|
||||
const char* str = luaL_checklstring(L, 2, &len);
|
||||
if(fcntl(fd, F_GETFD) < 0)
|
||||
L_RETURN_ERROR("Invalid FD");
|
||||
lua_pushinteger(L, write(fd, str, len));
|
||||
return 1;
|
||||
}
|
||||
|
||||
//* setNonBlocking(int fd) Sets a file descriptor to be non-blocking
|
||||
static int l_setNonBlocking(lua_State* L){
|
||||
int fd = luaL_checkinteger(L, 1);
|
||||
if(fcntl(fd, F_GETFD) < 0)
|
||||
L_RETURN_ERROR("Invalid FD");
|
||||
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct luaL_Reg l_cutillib[] = {
|
||||
{"getFD", l_getFD},
|
||||
{"spawn", l_spawn},
|
||||
{"read", l_read},
|
||||
{"write", l_write},
|
||||
{"close", l_close},
|
||||
{"setNonBlocking", l_setNonBlocking},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int luaopen_c(lua_State* L){
|
||||
lua_newtable(L);
|
||||
for(int i = 0; l_cutillib[i].func; i++){
|
||||
lua_pushcfunction(L, l_cutillib[i].func);
|
||||
lua_setfield(L, -2, l_cutillib[i].name);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
35
src/common.h
Normal file
35
src/common.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/* This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ */
|
||||
#define L_SET_INT(s, i, t) { \
|
||||
lua_pushstring(L, s); \
|
||||
lua_pushinteger(L, i); \
|
||||
lua_rawset(L, t); \
|
||||
}
|
||||
#define L_SET_BOOL(s, i, t) { \
|
||||
lua_pushstring(L, s); \
|
||||
lua_pushboolean(L, i); \
|
||||
lua_rawset(L, t); \
|
||||
}
|
||||
#define L_SET_STR(s, i, t) { \
|
||||
lua_pushstring(L, s); \
|
||||
lua_pushstring(L, i); \
|
||||
lua_rawset(L, t); \
|
||||
}
|
||||
#define L_SET_LSTRING(s, i, size, t) { \
|
||||
lua_pushstring(L, s); \
|
||||
lua_pushlstring(L, i, size); \
|
||||
lua_rawset(L, t); \
|
||||
}
|
||||
#define L_RETURN_ERROR(string, ...) { \
|
||||
lua_pushnil(L); \
|
||||
lua_pushfstring(L, string" :(", ##__VA_ARGS__); \
|
||||
return 2; \
|
||||
}
|
||||
#define L_THROW_ERROR(string) { \
|
||||
lua_pushstring(L, string); \
|
||||
lua_error(L); \
|
||||
}
|
||||
|
||||
#if LUA_VERSION_NUM >= 502
|
||||
#define lua_objlen lua_rawlen
|
||||
#endif
|
||||
|
||||
543
src/x.c
Normal file
543
src/x.c
Normal file
|
|
@ -0,0 +1,543 @@
|
|||
/* This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ */
|
||||
//* .name Xlib bindings
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <setjmp.h>
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#ifdef L_XRANDR
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
#else
|
||||
#include <X11/extensions/Xinerama.h>
|
||||
#endif
|
||||
#include "common.h"
|
||||
|
||||
static Display* display;
|
||||
static Window root;
|
||||
static lua_State* gL;
|
||||
static int lastXErrorCode;
|
||||
|
||||
#define REGISTRY_ERROR_HANDLER "thornWM.xErrorHandler"
|
||||
|
||||
// --- ERROR ---
|
||||
static int onXError(Display* display, XErrorEvent* err){
|
||||
char buffer[1024];
|
||||
XGetErrorText(display, err->error_code, buffer, 1023);
|
||||
/* fprintf(stderr, "X error: %s\n\tcode: %u, request: %u.%u\n", buffer, err->error_code, err->request_code, err->minor_code); */
|
||||
lastXErrorCode = err->error_code;
|
||||
lua_pushstring(gL, REGISTRY_ERROR_HANDLER);
|
||||
lua_rawget(gL, LUA_REGISTRYINDEX);
|
||||
/* printf("type: %s\n", lua_typename(gL, lua_type(gL, -1))); */
|
||||
if(lua_type(gL, -1) == LUA_TFUNCTION){
|
||||
printf("pushing error handler args\n");
|
||||
lua_pushfstring(gL, "X error: %s\n\tcode: %u, request: %u.%u\n", err->error_code, err->request_code, err->minor_code);
|
||||
lua_pushinteger(gL, err->error_code);
|
||||
lua_call(gL, 2, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
//* setErrorHandler(function handler) Fires when Xlib reports an error
|
||||
static int l_setErrorHandler(lua_State* L){
|
||||
/* printf("setting error hnalder %p %p\n", L, gL); */
|
||||
if(lua_type(L, 1) != LUA_TFUNCTION)
|
||||
luaL_argerror(L, 1, "Function required");
|
||||
lua_pushstring(L, REGISTRY_ERROR_HANDLER);
|
||||
lua_rawset(L, LUA_REGISTRYINDEX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- ATOMS ---
|
||||
//* atom atom, [atom atom, ...] getAtoms(str name, [str name, ...]) Fetches atom(s) by name. Atoms are ints with names for use in X
|
||||
static int l_getAtoms(lua_State* L){
|
||||
int top = lua_gettop(L);
|
||||
bool onlyIfExists = false;
|
||||
if(lua_type(L, top) == LUA_TBOOLEAN)
|
||||
onlyIfExists = lua_toboolean(L, top--);
|
||||
char* names[top];
|
||||
for(int i = 0; i < top; i++)
|
||||
names[i] = (char*)luaL_checkstring(L, i+1);
|
||||
Atom atoms[top];
|
||||
XInternAtoms(display, names, top, onlyIfExists, atoms);
|
||||
for(int i = 0; i < top; i++){
|
||||
if(atoms[i])
|
||||
lua_pushinteger(L, atoms[i]);
|
||||
else
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return top;
|
||||
}
|
||||
//* str name, [str name, ...] getAtomNames(atom atom, [atom atom, ...]) Fetches atom name(s)
|
||||
static int l_getAtomNames(lua_State* L){
|
||||
int top = lua_gettop(L);
|
||||
Atom atoms[top];
|
||||
for(int i = 0; i < top; i++)
|
||||
atoms[i] = luaL_checkinteger(L, i+1);
|
||||
char* names[top];
|
||||
XGetAtomNames(display, atoms, top, (char**)(&names));
|
||||
for(int i = 0; i < top; i++)
|
||||
lua_pushstring(L, names[i]);
|
||||
return top;
|
||||
}
|
||||
|
||||
// --- PROPERTIES ---
|
||||
//* str prop | int prop getProperty(int win, atom prop, int offset, atom type) Fetches a property. Returns one or more ints/strings. **type** can be **AnyPropertyType**
|
||||
static int l_getProperty(lua_State* L){
|
||||
Window win = luaL_checkinteger(L, 1);
|
||||
Atom prop = luaL_checkinteger(L, 2);
|
||||
long long offset = luaL_optnumber(L, 3, 0);
|
||||
Atom type = luaL_optnumber(L, 4, 0);
|
||||
Atom actualReturnType;
|
||||
int actualReturnFormat;
|
||||
unsigned long numberReturned;
|
||||
unsigned long excessBytes;
|
||||
unsigned char* propReturn;
|
||||
XGetWindowProperty(display, win, prop, offset, 4096, false,
|
||||
type, &actualReturnType, &actualReturnFormat, &numberReturned, &excessBytes, &propReturn);
|
||||
if(!actualReturnType)
|
||||
L_RETURN_ERROR("Property does not exist");
|
||||
if(!numberReturned)
|
||||
L_RETURN_ERROR("Wrong property type");
|
||||
int strings = 0;
|
||||
char* str = (char*)propReturn;
|
||||
switch(actualReturnFormat){
|
||||
case 32:
|
||||
for(int i = 0; i < numberReturned; i++){
|
||||
lua_pushinteger(L, *(((int32_t*)propReturn)+(i*2)));
|
||||
}
|
||||
return numberReturned;
|
||||
case 8:
|
||||
do{
|
||||
lua_pushstring(L, (char*)str);
|
||||
strings++;
|
||||
} while((((str = strchr(str, '\0')+1) - (char*)propReturn) < numberReturned-1));
|
||||
return strings;
|
||||
case 16:
|
||||
/* printf("got a 16 bit value!!!"); */
|
||||
for(int i = 0; i < numberReturned; i++){
|
||||
lua_pushinteger(L, *(((int16_t*)propReturn)+(i*2))); // untested. idk where a 16 bit value is
|
||||
}
|
||||
return numberReturned;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
//* setProperty(int win, atom prop, atom type, int format, int mode, table|str|int value) Sets a property. Accepts multiple values in a table. **mode** is one of **PropModeAppend**, **PropModePrepend**, **PropModeReplace**
|
||||
static int l_setProperty(lua_State* L){
|
||||
Window win = luaL_checkinteger(L, 1);
|
||||
Atom prop = luaL_checkinteger(L, 2);
|
||||
Atom type = luaL_checkinteger(L, 3);
|
||||
int format = luaL_checkinteger(L, 4);
|
||||
int mode = luaL_checkinteger(L, 5);
|
||||
unsigned char data[4096] = {'\0'}; // this can overflow FIXME
|
||||
size_t dataTop = 0;
|
||||
bool isTable = lua_istable(L, 6);
|
||||
int len = isTable ? lua_objlen(L, 6) : 1;
|
||||
int start = 6 + isTable;
|
||||
for(int i = 0; i < len; i++){
|
||||
if(isTable)
|
||||
lua_rawgeti(L, 6, i+1);
|
||||
const char* str; int num;
|
||||
switch(lua_type(L, start+i)){
|
||||
case LUA_TSTRING:
|
||||
str = lua_tostring(L, start+i);
|
||||
num = lua_objlen(L, start+i);
|
||||
memcpy(data+dataTop, str, num);
|
||||
dataTop += num;
|
||||
data[dataTop++] = '\0';
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
if(format == 32){
|
||||
*((int32_t*)data+i*2) = lua_tointeger(L, start+i);
|
||||
dataTop += 4;
|
||||
} else {
|
||||
*((int16_t*)data+i*2) = lua_tointeger(L, start+i);
|
||||
dataTop += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* lua_pushlstring(L, data, dataTop); */
|
||||
XChangeProperty(display, win, prop, type, format, mode, data, dataTop/(format/8));
|
||||
return 0;
|
||||
}
|
||||
//* delProperty(int win, atom prop) Deletes a property.
|
||||
static int l_delProperty(lua_State* L){
|
||||
XDeleteProperty(display, luaL_checkinteger(L, 1), luaL_checkinteger(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- WINDOW INFORMATION ---
|
||||
//* table children getWindowChildren([int win]) Returns a table of child ids. Defaults to the root window
|
||||
static int l_getWindowChildren(lua_State* L){
|
||||
Window win = luaL_optnumber(L, 1, root);
|
||||
unsigned wnum;
|
||||
Window d1, d2;
|
||||
Window* wins;
|
||||
if(!XQueryTree(display, win, &d1, &d2, &wins, &wnum))
|
||||
return 0;
|
||||
lua_newtable(L);
|
||||
for(int w = 0; w < wnum; w++){
|
||||
lua_pushinteger(L, wins[w]);
|
||||
}
|
||||
if(wins)
|
||||
XFree(wins);
|
||||
return 1;
|
||||
}
|
||||
//* table attrs getWindowAttributes([int win]) Returns a window's **id**, **mapState**, **x**, **y**, **width**, **height**, **depth**, and **borderWidth** in a table. Defaults to the root window
|
||||
static int l_getWindowAttributes(lua_State* L){
|
||||
Window win = luaL_optnumber(L, 1, root);
|
||||
XWindowAttributes wa;
|
||||
if(!XGetWindowAttributes(display, win, &wa))
|
||||
return 0;
|
||||
lua_newtable(L);
|
||||
L_SET_INT("id", win, -3);
|
||||
L_SET_INT("mapState", wa.map_state, -3);
|
||||
L_SET_INT("x", wa.x, -3);
|
||||
L_SET_INT("y", wa.y, -3);
|
||||
L_SET_INT("width", wa.width, -3);
|
||||
L_SET_INT("height", wa.height, -3);
|
||||
L_SET_INT("depth", wa.depth, -3);
|
||||
L_SET_INT("borderWidth", wa.border_width, -3);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// --- WINDOW APPEARANCE ---
|
||||
//* setBorderWidth(int win, int width) Sets a window's border width in pixels
|
||||
static int l_setBorderWidth(lua_State* L){
|
||||
XSetWindowBorderWidth(display, luaL_checkinteger(L, 1), luaL_checkinteger(L, 2));
|
||||
return 0;
|
||||
}
|
||||
//* setBorderColour(int win, int colour) Sets a window's border colour. **colour** is in the form of 0xRRGGBB
|
||||
static int l_setBorderColour(lua_State* L){
|
||||
XSetWindowBorder(display, luaL_checkinteger(L, 1), luaL_checkinteger(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- WINDOW GEOMETRY ---
|
||||
//* mapWindow(int win) Maps a window to the screen
|
||||
static int l_mapWindow(lua_State* L){
|
||||
XMapWindow(display, luaL_checkinteger(L, 1));
|
||||
return 0;
|
||||
}
|
||||
//* unmapWindow(int win) Unmaps a window from the screen
|
||||
static int l_unmapWindow(lua_State* L){
|
||||
printf("unmpaping window\n");
|
||||
XUnmapWindow(display, luaL_checkinteger(L, 1));
|
||||
return 0;
|
||||
}
|
||||
//* setWindowGeometry(int win, int x, int y, int width, int height) Sets a window's geometry
|
||||
static int l_setWindowGeometry(lua_State* L){
|
||||
XMoveResizeWindow(display, luaL_checkinteger(L, 1), luaL_checkinteger(L, 2),
|
||||
luaL_checkinteger(L, 3), luaL_checkinteger(L, 4), luaL_checkinteger(L, 5));
|
||||
return 0;
|
||||
}
|
||||
//* setWindowSize(int win, int width, int height) Sets a window's size
|
||||
static int l_setWindowSize(lua_State* L){
|
||||
XResizeWindow(display, luaL_checkinteger(L, 1), luaL_checkinteger(L, 2), luaL_checkinteger(L, 3));
|
||||
return 0;
|
||||
}
|
||||
//* setWindowPosition(int win, int x, int y) Sets a window's coordinates
|
||||
static int l_setWindowPosition(lua_State* L){
|
||||
XMoveWindow(display, luaL_checkinteger(L, 1), luaL_checkinteger(L, 2), luaL_checkinteger(L, 3));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- WINDOW OTHER ---
|
||||
//* pingWindow(int win) Pings a window to see if it's still processing X events. See https://specifications.freedesktop.org/wm-spec/1.3/ar01s06.html
|
||||
static int l_pingWindow(lua_State* L){
|
||||
Window win = luaL_checkinteger(L, 1);
|
||||
Atom atoms[2];
|
||||
char* names[2] = {"WM_PROTOCOLS", "_NET_WM_PING"};
|
||||
XInternAtoms(display, names, 2, true, atoms);
|
||||
XEvent ev;
|
||||
ev.type = ClientMessage;
|
||||
ev.xclient.type = ClientMessage;
|
||||
ev.xclient.window = win;
|
||||
ev.xclient.message_type = atoms[0];
|
||||
ev.xclient.data.l[0] = atoms[1];
|
||||
/* ev.xclient.data.l[1] = CurrentTime(); */
|
||||
ev.xclient.data.l[2] = win;
|
||||
printf("XSendEvent %i\n", XSendEvent(display, win, false, 0, &ev));
|
||||
return 0;
|
||||
}
|
||||
//* setInputFocus(int win) Sets the input focus for the keyboard
|
||||
static int l_setInputFocus(lua_State* L){
|
||||
Window win = luaL_checkinteger(L, 1);
|
||||
XSetInputFocus(display, win, RevertToParent, CurrentTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- MONITORS ---
|
||||
//* table monitors getMonitors() Returns a list of monitors with their geometry
|
||||
static int l_getMonitors(lua_State* L){
|
||||
lua_newtable(L);
|
||||
#ifdef L_XRANDR
|
||||
XRRScreenResources *res = XRRGetScreenResources(display, root); // this is the most fucking cursed fuck fucking bullshit
|
||||
for(int i = 0; i < res->ncrtc; i++){
|
||||
XRRCrtcInfo* info = XRRGetCrtcInfo(display, res, res->crtcs[i]);
|
||||
if(!info->width){
|
||||
XRRFreeCrtcInfo(info);
|
||||
break;
|
||||
}
|
||||
lua_newtable(L);
|
||||
L_SET_INT("width", info->width, -3);
|
||||
L_SET_INT("height", info->height, -3);
|
||||
L_SET_INT("x", info->x, -3);
|
||||
L_SET_INT("y", info->y, -3);
|
||||
XRRFreeCrtcInfo(info);
|
||||
lua_rawseti(L, -2, i+1);
|
||||
}
|
||||
XRRFreeScreenResources(res);
|
||||
#else
|
||||
int num;
|
||||
XineramaScreenInfo* info = XineramaQueryScreens(display, &num);
|
||||
for(int i = 0; i < num; i++){
|
||||
lua_newtable(L);
|
||||
L_SET_INT("width", info[i].width, -3);
|
||||
L_SET_INT("height", info[i].height, -3);
|
||||
L_SET_INT("x", info[i].x_org, -3);
|
||||
L_SET_INT("y", info[i].y_org, -3);
|
||||
lua_rawseti(L, -2, i+1);
|
||||
}
|
||||
XFree(info);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
// --- INPUT ---
|
||||
//* grabKey(str key, int mask) Grabs a key combo to receive events from it. **key** is a keysym, see https://wiki.linuxquestions.org/wiki/List_of_keysyms for a list. **mask** is a list of modifiers added together
|
||||
static int l_grabKey(lua_State* L){
|
||||
const char* key = luaL_checkstring(L, 1);
|
||||
int mask = luaL_checkinteger(L, 2);
|
||||
KeySym keysim = XStringToKeysym(key);
|
||||
if(!keysim)
|
||||
L_RETURN_ERROR("KeySim not found for '%s'", key)
|
||||
KeyCode keycode = XKeysymToKeycode(display, keysim);
|
||||
XGrabKey(display, keycode, mask, root, true, GrabModeAsync, GrabModeAsync);
|
||||
return 0;
|
||||
}
|
||||
//* releaseKey(str key, int mask) Stop receiving key events
|
||||
static int l_releaseKey(lua_State* L){
|
||||
return 0;
|
||||
}
|
||||
//* grabMouseButton(int button, int mask) Start receiving events about one of the three mouse buttons. **button** is one of the three mouse buttons, and **mask** is the same as with *grabKey*
|
||||
static int l_grabMouseButton(lua_State* L){
|
||||
int button = luaL_checkinteger(L, 1);
|
||||
int mask = luaL_checkinteger(L, 2);
|
||||
XGrabButton(display, button, mask, root, true, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);
|
||||
return 0;
|
||||
}
|
||||
//* releaseMouseButton(int button, int mask) Stop receiving mouse button events
|
||||
static int l_releaseMouseButton(lua_State* L){
|
||||
return 0;
|
||||
}
|
||||
//* grabMousePointer() Start receiving events from mouse movement
|
||||
static int l_grabMousePointer(lua_State* L){
|
||||
XGrabPointer(display, root, true, PointerMotionMask|ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
|
||||
return 0;
|
||||
}
|
||||
//* releaseMousePointer() Stop receiving events from mouse movement
|
||||
static int l_releaseMousePointer(lua_State* L){
|
||||
XUngrabPointer(display, CurrentTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- EVENTS ---
|
||||
//* table event nextEvent() Read an event from the queue
|
||||
int l_nextEvent(lua_State* L){
|
||||
XEvent ev;
|
||||
XNextEvent(display, &ev);
|
||||
lua_newtable(L);
|
||||
L_SET_INT("type", ev.type, -3);
|
||||
switch(ev.type){
|
||||
case MapRequest:
|
||||
L_SET_INT("id", ev.xmaprequest.window, -3);
|
||||
L_SET_INT("parent", ev.xmaprequest.parent, -3);
|
||||
return 1;
|
||||
case DestroyNotify:
|
||||
L_SET_INT("id", ev.xdestroywindow.window, -3);
|
||||
return 1;
|
||||
case KeyPress:
|
||||
case KeyRelease:
|
||||
L_SET_STR("key", XKeysymToString(XLookupKeysym(&ev.xkey, 0)), -3);
|
||||
L_SET_INT("mask", ev.xkey.state, -3);
|
||||
return 1;
|
||||
case ClientMessage:
|
||||
printf("got a client message");
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// --- META ---
|
||||
//* int root open([str display]) Opens the connection to the X server. This needs to be called before any other function, otherwise the program will segfault. **display** is which display should be connected to, and defaults to **$DISPLAY**. Returns the root window id
|
||||
static int l_open(lua_State* L){
|
||||
display = XOpenDisplay(lua_isnoneornil(L, 1) ? NULL : luaL_checkstring(L, 1));
|
||||
if(!display)
|
||||
L_RETURN_ERROR("Failed to connect to X");
|
||||
root = DefaultRootWindow(display);
|
||||
XSetErrorHandler(onXError);
|
||||
lastXErrorCode = 0;
|
||||
XSelectInput(display, root, SubstructureRedirectMask | SubstructureNotifyMask);
|
||||
XSync(display, root);
|
||||
if(lastXErrorCode)
|
||||
L_RETURN_ERROR("Failed to get SubstructureRedirectMask. Is a WM already running?");
|
||||
#if L_XRANDR
|
||||
XRRSelectInput(display, root, RRScreenChangeNotifyMask);
|
||||
#endif
|
||||
lua_pushinteger(L, root);
|
||||
return 1;
|
||||
}
|
||||
//* close() Closes the X server connection
|
||||
static int l_close(lua_State* L){
|
||||
XCloseDisplay(display);
|
||||
return 0;
|
||||
}
|
||||
//* sync([bool discard]) Flushes the output buffer and waits until all requests have been received and processed by the X server. **discard** is whether it should discard all events on the event queue and defaults to false
|
||||
static int l_sync(lua_State* L){
|
||||
XSync(display, lua_toboolean(L, 1));
|
||||
return 0;
|
||||
}
|
||||
//* int number pending() Returns the number of events enqueued
|
||||
static int l_pending(lua_State* L){
|
||||
lua_pushinteger(L, XPending(display));
|
||||
return 1;
|
||||
}
|
||||
//* str version getVersion() Returns a string with X component versions
|
||||
static int l_getVersion(lua_State* L){
|
||||
int xrmajor, xrminor;
|
||||
#ifdef L_XRANDR
|
||||
#define MMEXT "Xrandr"
|
||||
XRRQueryVersion(display, &xrmajor, &xrminor);
|
||||
#else
|
||||
#define MMEXT "Xinerama"
|
||||
XineramaQueryVersion(display, &xrmajor, &xrminor);
|
||||
#endif
|
||||
lua_pushfstring(L, "%s %d %d.%d - %s - "MMEXT" %d.%d",
|
||||
ServerVendor(display), VendorRelease(display), ProtocolVersion(display), ProtocolRevision(display),
|
||||
DisplayString(display),
|
||||
xrmajor, xrminor);
|
||||
#undef MMEXT
|
||||
return 1;
|
||||
}
|
||||
//* int id createWindow() Creates a simple window
|
||||
static int l_createWindow(lua_State* L){
|
||||
Window win = XCreateSimpleWindow(display, root, 0, 0, 1, 1, 0, 0, 0);
|
||||
lua_pushinteger(L, win);
|
||||
return 1;
|
||||
}
|
||||
//* int fd getFD() Returns the file descriptor referencing the X socket
|
||||
static int l_getFD(lua_State* L){
|
||||
lua_pushinteger(L, XConnectionNumber(display));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct luaL_Reg l_xlib[] = {
|
||||
// ERROR
|
||||
{"setErrorHandler", l_setErrorHandler},
|
||||
// ATOMS
|
||||
{"getAtoms", l_getAtoms},
|
||||
{"getAtomNames", l_getAtomNames},
|
||||
// PROPERTIES
|
||||
{"getProperty", l_getProperty},
|
||||
{"setProperty", l_setProperty},
|
||||
{"delProperty", l_delProperty},
|
||||
// WINDOW INFORMATION
|
||||
{"getWindowChildren", l_getWindowChildren},
|
||||
{"getWindowAttributes", l_getWindowAttributes},
|
||||
// WINDOW APPEARANCE
|
||||
{"setBorderWidth", l_setBorderWidth},
|
||||
{"setBorderColour", l_setBorderColour},
|
||||
// WINDOW MANIPULATION
|
||||
{"mapWindow", l_mapWindow},
|
||||
{"unmapWindow", l_unmapWindow},
|
||||
{"setWindowGeometry", l_setWindowGeometry},
|
||||
{"setWindowSize", l_setWindowSize},
|
||||
{"setWindowPosition", l_setWindowPosition},
|
||||
// WINDOW OTHER
|
||||
{"pingWindow", l_pingWindow},
|
||||
{"setInputFocus", l_setInputFocus},
|
||||
// MONITORS
|
||||
{"getMonitors", l_getMonitors},
|
||||
// INPUT
|
||||
{"grabKey", l_grabKey},
|
||||
{"releaseKey", l_releaseKey},
|
||||
{"grabMouseButton", l_grabMouseButton},
|
||||
{"releaseMouseButton", l_releaseMouseButton},
|
||||
{"grabMousePointer", l_grabMousePointer},
|
||||
{"releaseMousePointer", l_releaseMousePointer},
|
||||
// EVENTS
|
||||
{"nextEvent", l_nextEvent},
|
||||
// META
|
||||
{"open", l_open},
|
||||
{"close", l_close},
|
||||
{"sync", l_sync},
|
||||
{"pending", l_pending},
|
||||
{"getVersion", l_getVersion},
|
||||
{"createWindow", l_createWindow},
|
||||
{"getFD", l_getFD},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int luaopen_x(lua_State* L){
|
||||
gL = L;
|
||||
lua_newtable(L);
|
||||
for(int i = 0; l_xlib[i].func; i++){
|
||||
lua_pushcfunction(L, l_xlib[i].func);
|
||||
lua_setfield(L, -2, l_xlib[i].name);
|
||||
}
|
||||
// KEY MASKS
|
||||
//* .desc .TP
|
||||
//* .desc Key modifiers:
|
||||
//* .desc AnyMod, Control, Lock, Mod1, Mod2, Mod3, Mod4, Mod5, Shift
|
||||
L_SET_INT("AnyMod", AnyModifier, -3);
|
||||
L_SET_INT("Control", ControlMask, -3);
|
||||
L_SET_INT("Lock", LockMask, -3);
|
||||
L_SET_INT("Mod1", Mod1Mask, -3);
|
||||
L_SET_INT("Mod2", Mod2Mask, -3);
|
||||
L_SET_INT("Mod3", Mod3Mask, -3);
|
||||
L_SET_INT("Mod4", Mod4Mask, -3);
|
||||
L_SET_INT("Mod5", Mod5Mask, -3);
|
||||
L_SET_INT("Shift", ShiftMask, -3);
|
||||
// EVENTS
|
||||
//* .desc .TP
|
||||
//* .desc Event types:
|
||||
//* .desc ButtonPress, ButtonRelease, ClientMessage, ClientMessage, ConfigureRequest, CreateNotify, DestroyNotify, EnterNotify, FocusIn, FocusOut, KeyPress, KeyRelease, LastEvent, LeaveNotify, MapNotify, MapRequest, MappingNotify, MotionNotify, PropertyNotify, ResizeRequest, UnmapNotify, RRScreenChangeNotify
|
||||
L_SET_INT("ButtonPress", ButtonPress, -3);
|
||||
L_SET_INT("ButtonRelease", ButtonRelease, -3);
|
||||
L_SET_INT("ClientMessage", ClientMessage, -3);
|
||||
L_SET_INT("ConfigureRequest", ConfigureRequest, -3);
|
||||
L_SET_INT("CreateNotify", CreateNotify, -3);
|
||||
L_SET_INT("DestroyNotify", DestroyNotify, -3);
|
||||
L_SET_INT("EnterNotify", EnterNotify, -3);
|
||||
L_SET_INT("FocusIn", FocusIn, -3);
|
||||
L_SET_INT("FocusOut", FocusOut, -3);
|
||||
L_SET_INT("KeyPress", KeyPress, -3);
|
||||
L_SET_INT("KeyRelease", KeyRelease, -3);
|
||||
L_SET_INT("LastEvent", LASTEvent, -3);
|
||||
L_SET_INT("LeaveNotify", LeaveNotify, -3);
|
||||
L_SET_INT("MapNotify", MapNotify, -3);
|
||||
L_SET_INT("MapRequest", MapRequest, -3);
|
||||
L_SET_INT("MappingNotify", MappingNotify, -3);
|
||||
L_SET_INT("MotionNotify", MotionNotify, -3);
|
||||
L_SET_INT("PropertyNotify", PropertyNotify, -3);
|
||||
L_SET_INT("ResizeRequest", ResizeRequest, -3);
|
||||
L_SET_INT("UnmapNotify", UnmapNotify, -3);
|
||||
#if L_XRANDR
|
||||
L_SET_INT("RRScreenChangeNotify", RRScreenChangeNotify, -3);
|
||||
#endif
|
||||
// PROPERTY ENUMS
|
||||
//* .desc .TP
|
||||
//* .desc Property enums:
|
||||
//* .desc AnyPropertyType, PropModeAppend, PropModePrepend, PropModeReplace
|
||||
L_SET_INT("AnyPropertyType", AnyPropertyType, -3);
|
||||
L_SET_INT("PropModeAppend", PropModeAppend, -3);
|
||||
L_SET_INT("PropModePrepend", PropModePrepend, -3);
|
||||
L_SET_INT("PropModeReplace", PropModeReplace, -3);
|
||||
return 1;
|
||||
}
|
||||
106
thornWM
Executable file
106
thornWM
Executable file
|
|
@ -0,0 +1,106 @@
|
|||
#!/bin/env luajit
|
||||
-- This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ --
|
||||
-- Entry point. Handles cli arguments and imports and sets up other modules
|
||||
local thornWMVersion = "0.1.0"
|
||||
do
|
||||
-- local p = arg[0]:match("(.-)[^\\/]+$").."lib/" -- add pathOfScript/lib to path
|
||||
-- package.path = p .. "?.lua;" .. package.path .. ";./lib/?.lua;./lib/?/init.lua;/usr/share/thornWM/lib/?.lua;/usr/local/share/thornWM/lib/?.lua"
|
||||
-- package.cpath = p .. "?.so;" .. package.cpath .. ";./lib/?.so;./lib/?/init.so;/usr/share/thornWM/lib/?.so;/usr/local/share/thornWM/lib/?.so"
|
||||
-- local version = _VERSION:match("%d%.%d") -- add luarocks to path
|
||||
-- package.cpath = package.cpath .. string.format(";%s/.luarocks/lib/lua/%s/?.so;/usr/local/lib/lua/%s/?.so", os.getenv("HOME"), version, version)
|
||||
for t,x in pairs({ path = "lua", cpath = "so" }) do
|
||||
package[t] = table.concat({
|
||||
package[t],
|
||||
"./lib/?."..x, "./lib/?/init."..x,
|
||||
"/usr/share/thornWM/lib/?."..x, "/usr/share/thornWM/lib/?/init.lua."..x,
|
||||
"/usr/locale/share/thornWM/lib/?."..x, "/usr/local/share/thornWM/lib/?/init.lua."..x,
|
||||
}, ";")
|
||||
end
|
||||
-- package.path = package.path .. "/usr/share/lua/5.1/?.lua;;/home/eiko/.luarocks/share/lua/5.1/?.lua;/home/eiko/.luarocks/share/lua/5.1/?/init.lua;/usr/share/lua/5.1/?/init.lua"
|
||||
-- package.cpath = package.cpath .. "/usr/lib/lua/5.1/?.so;/usr/lib/lua/5.1/loadall.so;./?.so;/root/.luarocks/lib/lua/5.1/?.so;/home/eiko/.luarocks/lib/lua/5.1/?.so"
|
||||
end
|
||||
|
||||
local Async = require("async")
|
||||
|
||||
local cmdArgs = {}
|
||||
local skipNext
|
||||
for i=1,#arg do
|
||||
if skipNext then
|
||||
skipNext = false
|
||||
else
|
||||
local a = arg[i]
|
||||
if a == "-c" or a == "--config" then
|
||||
cmdArgs.config = arg[i+1]
|
||||
skipNext = true
|
||||
elseif a == "-d" or a == "--display" then
|
||||
cmdArgs.display = arg[i+1]
|
||||
skipNext = true
|
||||
elseif a == "-v" or a == "--version" then -- TODO might want to add a git hash here too
|
||||
local ok, X = pcall(require, "x")
|
||||
local xVersion
|
||||
if ok then
|
||||
X.open(cmdArgs.display)
|
||||
X.setErrorHandler(print)
|
||||
X.setWindowGeometry(213, 0, 0,0, 0)
|
||||
xVersion = X.getVersion()
|
||||
else
|
||||
xVersion = "[Failed to load X]"
|
||||
end
|
||||
io.stderr:write(string.format(
|
||||
"thornWM v%s#%s\n%s %s\n%s\nlua-ev v%d.%d\nLicense: Mozilla Public License 2.0\n",
|
||||
thornWMVersion, "8a00ff", _VERSION, arg[-1], xVersion, Async.ev.version()
|
||||
))
|
||||
os.exit(0)
|
||||
elseif a == "-h" or a == "--help" then
|
||||
io.stderr:write((
|
||||
"Usage: %s [-c configPath] [-d displayNumber] [-h | -v]\n"..
|
||||
" -h --help\n"..
|
||||
" -v --version\n"..
|
||||
" -d --display displayNumber\n"..
|
||||
" -c --config configPath\n"
|
||||
):format(arg[0]:match("[^/]+$")))
|
||||
os.exit(0)
|
||||
else
|
||||
io.stderr:write(string.format("thornWM: Unkown argument '%s'\n", a))
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local X = require("x")
|
||||
local C = require("c")
|
||||
local Tree = require("tree")
|
||||
local Conf = require("conf")
|
||||
local EWMH = require("ewmh")
|
||||
local Event = require("event")
|
||||
_G.inspect = require("inspect")
|
||||
_G.p = function(...) print(inspect(...)) end
|
||||
-- Debug = require("thdebug") -- Not generally useful except for development
|
||||
|
||||
X.root = assert(X.open(cmdArgs.display))
|
||||
X.lastError = 0
|
||||
EWMH.init("thornWM")
|
||||
Conf.cmdArgs = cmdArgs
|
||||
Conf.init()
|
||||
Tree.init()
|
||||
|
||||
X.setErrorHandler(function(str, code)
|
||||
print(str, code)
|
||||
io.stderr:write(string.format("thornWM: Got X Error: %s\n", str))
|
||||
X.lastError = code
|
||||
end)
|
||||
|
||||
Async.fd(X.getFD(), Async.ev.READ, Event.handleXEvents)
|
||||
Async.fd(0, Async.ev.READ, function()
|
||||
local mes = C.read(0)
|
||||
local f, err = loadstring(mes)
|
||||
if not f then return io.stderr:write(err.."\n") end
|
||||
f()
|
||||
end)
|
||||
|
||||
-- Async.idle(function()
|
||||
-- end)
|
||||
|
||||
C.setNonBlocking(0)
|
||||
Event.handleXEvents()
|
||||
Async.loop()
|
||||
Loading…
Add table
Add a link
Reference in a new issue