├── .github
├── FUNDING.yml
└── workflows
│ └── build-and-release.yaml
├── .gitignore
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── doc
├── spacebar.1
└── spacebar.asciidoc
├── examples
└── spacebarrc
├── flake.lock
├── flake.nix
├── makefile
└── src
├── application.c
├── application.h
├── application_manager.c
├── application_manager.h
├── bar.c
├── bar.h
├── bar_manager.c
├── bar_manager.h
├── display.c
├── display.h
├── display_manager.c
├── display_manager.h
├── event.c
├── event.h
├── event_loop.c
├── event_loop.h
├── manifest.m
├── message.c
├── message.h
├── misc
├── dnd.c
├── hashtable.h
├── helpers.h
├── log.h
├── macros.h
├── memory_pool.h
├── notify.h
├── sbuffer.h
├── socket.c
├── socket.h
└── timing.h
├── process_manager.c
├── process_manager.h
├── spacebar.c
├── window.c
├── window.h
├── workspace.h
└── workspace.m
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [cmacrae]
2 |
--------------------------------------------------------------------------------
/.github/workflows/build-and-release.yaml:
--------------------------------------------------------------------------------
1 | name: Build & Release
2 |
3 | on:
4 | push:
5 | branches: []
6 | tags:
7 | - 'v*'
8 | - "testing"
9 | paths:
10 | - 'src/**'
11 | - '.github/**'
12 |
13 | jobs:
14 | build-and-release:
15 | runs-on: macos-11
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v2
19 | with:
20 | # Flakes don't work on shallow clones
21 | fetch-depth: 0
22 |
23 | - name: Configure Git
24 | run: |
25 | git config user.name "$GITHUB_ACTOR"
26 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
27 |
28 | - name: Install Nix
29 | uses: cachix/install-nix-action@v16
30 | with:
31 | extra_nix_config: |
32 | experimental-features = nix-command flakes
33 | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
34 |
35 | - name: Build
36 | id: build-bin
37 | run: |
38 | make all
39 |
40 | - name: Check execution & get version
41 | id: check-bin
42 | run: |
43 | echo "::set-output name=version::$(./bin/spacebar --version | cut -d- -f2)"
44 |
45 | - name: Build with Nix
46 | run: |
47 | nix build .
48 |
49 | - name: Get the current tag name
50 | if: ${{ startsWith(github.ref, 'refs/tags/') }}
51 | run: |
52 | echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
53 |
54 | - name: Check bin version before release
55 | if: ${{ startsWith(github.ref, 'refs/tags/') && !startsWith(github.ref, 'refs/tags/testing') }}
56 | run: |
57 | [[ "$RELEASE_VERSION" == ${{ steps.check-bin.outputs.version }} ]]
58 |
59 | - name: Create release archive
60 | if: ${{ startsWith(github.ref, 'refs/tags/') }}
61 | id: archive
62 | run: |
63 | tar czvf spacebar-$RELEASE_VERSION.tar.gz bin/spacebar doc examples
64 | echo "::set-output name=file::spacebar-$RELEASE_VERSION.tar.gz"
65 | echo "::set-output name=sha::$(shasum -a 256 spacebar-$RELEASE_VERSION.tar.gz | cut -d" " -f1)"
66 |
67 | - name: Create release
68 | if: ${{ startsWith(github.ref, 'refs/tags/') }}
69 | id: create-release
70 | uses: actions/create-release@v1
71 | env:
72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
73 | with:
74 | draft: ${{ startsWith(github.ref, 'refs/tags/testing') }}
75 | tag_name: ${{ github.ref }}
76 | release_name: ${{ env.RELEASE_VERSION }}
77 | body: |
78 | [Changelog](https://github.com/cmacrae/spacebar/blob/master/CHANGELOG.md)
79 |
80 | You can find a precompiled release tarball in the release assets below.
81 | SHA-256 checksum:
82 | `${{ steps.archive.outputs.sha }}`
83 |
84 | - name: Upload release archive
85 | if: ${{ startsWith(github.ref, 'refs/tags/') }}
86 | id: upload-release-asset
87 | uses: actions/upload-release-asset@v1
88 | env:
89 | GITHUB_TOKEN: ${{ github.token }}
90 | with:
91 | upload_url: ${{ steps.create-release.outputs.upload_url }}
92 | asset_path: ${{ steps.archive.outputs.file }}
93 | asset_name: ${{ steps.archive.outputs.file }}
94 | asset_content_type: application/gzip
95 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 | /archive
3 | /.idea
4 | result
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased](https://github.com/cmacrae/spacebar/compare/v1.4.0...HEAD)
8 |
9 | ## [1.4.0](https://github.com/cmacrae/spacebar/releases/tag/v1.4.0) - 2022-02-14
10 | **Changed**
11 | - Fixed a bug that caused segfaults & rendering issues on Monterey (thanks [@cxa](https://github.com/cxa)!)
12 | - Use Big Sur for CI builds
13 |
14 | **Added**
15 | - Support Apple Silicon for Nix package/flake
16 |
17 | ## [1.3.0](https://github.com/cmacrae/spacebar/releases/tag/v1.3.0) - 2020-04-25
18 | **Changed**
19 | - Fixed a bug introduced in Big Sur where buffer reads were incorrect
20 | - Improved efficiency of bar's initialisation
21 | - Fixed DoNotDisturb indicator on Big Sur
22 |
23 | **Added**
24 | - New `left|center|right` shell sections: display custom text based on shell pipelines
25 | - Option to turn focused window title display on or off (thanks [@Norviah](https://github.com/Norviah)!)
26 | - Option to turn the spaces indicator on or off
27 | - Option to turn the clock on or off
28 | - Option to turn the power indicator on or off
29 | - Option to turn the DoNotDisturb indicator on or off
30 | - Option to set the padding between the first/last item and the left/right edge of the display
31 | - Default to "Solid" style of Font Awesome 5 Free icon font
32 | - Option to display spaces for all displays, including: optional separator, secondary & tertiary space indicator colours in relation to display
33 | - Option to specify only drawing one bar on the main display or multiple bars, one for each display (this aligns with yabai's `external_bar` option)
34 | - Provide a [Nix Flake](https://nixos.wiki/wiki/Flakes)
35 |
36 | ## [1.2.1](https://github.com/cmacrae/spacebar/releases/tag/v1.2.1) - 2020-11-18
37 |
38 | **Changed**
39 | - Fixed a bug where querying for the value of `space_icon_strip` would set it to default (thanks [@jraregris](https://github.com/jraregris))
40 |
41 | ## [1.2.0](https://github.com/cmacrae/spacebar/releases/tag/v1.2.0) - 2020-11-18
42 |
43 | **Added**
44 | - Spacing configuration options
45 |
46 | ## [1.1.1](https://github.com/cmacrae/spacebar/releases/tag/v1.1.1) - 2020-07-21
47 |
48 | **Changed**
49 | - Fixed a bug where long window titles would draw over the right status area
50 | - Padding between items in the status area based on current values
51 |
52 | ## [1.1.0](https://github.com/cmacrae/spacebar/releases/tag/v1.1.0) - 2020-07-17
53 |
54 | **Added**
55 | - Height configuration option
56 |
57 | ## [1.0.0](https://github.com/cmacrae/spacebar/releases/tag/v1.0.0) - 2020-07-16
58 |
59 | **Added**
60 | - Option to position at the top or bottom of the screen
61 | - Individual colour settings for each icon in the right strip (`dnd`, `power`, `clock`)
62 | - DoNotDisturb indicator
63 |
64 | **Changed**
65 | - Current space indicated by colouring the glyph
66 | - Removal of underlines
67 | - Fixed flicker bug when changing monitor focus (thanks [@tom-auger](https://github.com/tom-auger))
68 |
69 | ## Pre-1.0.0
70 | This changelog was not kept up to date prior to `1.0.0`.
71 | See the commit log for more information.
72 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Calum MacRae
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | A minimal status bar for macOS
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ## About
35 | spacebar is a minimal status bar for macOS. Ideal for use with tiling window managers like [yabai](https://github.com/koekeishiya/yabai).
36 |
37 |
38 |
39 |
40 |
41 | ## Installation
42 | A package and service to install and manage spacebar is provided in two flavours: [Homebrew](https://brew.sh) & [Nix](https://nixos.org).
43 | There is also a precompiled binary archive available in [the latest release assets](https://github.com/cmacrae/spacebar/releases/tag/v1.4.0).
44 |
45 | ### Homebrew
46 | spacebar can be installed using Homebrew from the `cmacrae/formulae` tap
47 | ```
48 | brew install cmacrae/formulae/spacebar
49 | brew services start spacebar
50 | ```
51 |
52 | ### Nix
53 | A package is generally available to Nix users on macOS in the various channels.
54 | A [Flake](https://nixos.wiki/wiki/Flakes) is also available in this repository and can be used like so:
55 | ```nix
56 | {
57 | inputs.darwin.url = "github:lnl7/nix-darwin";
58 | inputs.spacebar.url = "github:cmacrae/spacebar/v1.4.0";
59 |
60 | outputs = { self, darwin, spacebar }: {
61 | darwinConfigurations.example = darwin.lib.darwinSystem {
62 | modules = [
63 | {
64 | nixpkgs.overlays = [
65 | spacebar.overlay
66 | ];
67 | }
68 | ];
69 | };
70 | };
71 | }
72 | ```
73 | Or try it out with `nix run github:cmacrae/spacebar/v1.4.0`!
74 |
75 | spacebar can be configured and managed in a declarative manner using the `services.spacebar` module in [nix-darwin](https://github.com/LnL7/nix-darwin)
76 |
77 | ### Accessibility Permissions
78 | spacebar makes use of the macOS Accessibility APIs - after starting spacebar, you should be prompted to grant access.
79 | Open System Preferences.app and navigate to Security & Privacy, then Privacy, then Accessibility. Click the lock icon at the bottom and enter your password to allow changes to the list. Check the box next to spacebar to allow accessibility permissions.
80 |
81 | ## Configuration
82 | spacebar is configured by setting `config` properties via its messaging socket. Not only does this mean you can try out config changes live, it also means spacebar's configuration file is simply a shell script - usually just a sequence of `spacebar -m config ` statements.
83 |
84 | spacebar's configuration file must executable and is looked for in the following locations (in this order) by default:
85 | * `$XDG_CONFIG_HOME/spacebar/spacebarrc`
86 | * `$HOME/.config/spacebar/spacebarrc`
87 | * `$HOME/.spacebarrc`
88 |
89 | ### Getting started
90 | To get started, create an empty configuration file and make it executable:
91 | ```
92 | mkdir -p ~/.config/spacebar
93 | touch ~/.config/spacebar/spacebarrc
94 | chmod +x ~/.config/spacebar/spacebarrc
95 | ```
96 |
97 | Here's a configuration taken from [`examples/`](https://github.com/cmacrae/spacebar/blob/v1.4.0/examples/spacebarrc):
98 | ```
99 | #!/usr/bin/env sh
100 |
101 | spacebar -m config position top
102 | spacebar -m config height 26
103 | spacebar -m config title on
104 | spacebar -m config spaces on
105 | spacebar -m config clock on
106 | spacebar -m config power on
107 | spacebar -m config padding_left 20
108 | spacebar -m config padding_right 20
109 | spacebar -m config spacing_left 25
110 | spacebar -m config spacing_right 15
111 | spacebar -m config text_font "Helvetica Neue:Bold:12.0"
112 | spacebar -m config icon_font "Font Awesome 5 Free:Solid:12.0"
113 | spacebar -m config background_color 0xff202020
114 | spacebar -m config foreground_color 0xffa8a8a8
115 | spacebar -m config space_icon_color 0xff458588
116 | spacebar -m config power_icon_color 0xffcd950c
117 | spacebar -m config battery_icon_color 0xffd75f5f
118 | spacebar -m config dnd_icon_color 0xffa8a8a8
119 | spacebar -m config clock_icon_color 0xffa8a8a8
120 | spacebar -m config power_icon_strip
121 | spacebar -m config space_icon_strip I II III IV V VI VII VIII IX X
122 | spacebar -m config space_icon
123 | spacebar -m config clock_icon
124 | spacebar -m config dnd_icon
125 | spacebar -m config clock_format "%d/%m/%y %R"
126 | spacebar -m config right_shell on
127 | spacebar -m config right_shell_icon
128 | spacebar -m config right_shell_command "whoami"
129 |
130 | echo "spacebar configuration loaded.."
131 |
132 | ```
133 | _Note: Ensure fonts are installed to use glyphs_
134 |
135 | For further configuration documentation, please see [`man spacebar`](https://github.com/cmacrae/spacebar/blob/v1.4.0/doc/spacebar.asciidoc)
136 |
137 | ### Declarative configuration with Nix
138 | If you're using the `services.spacebar` module from [nix-darwin](https://github.com/LnL7/nix-darwin), you can configure spacebar like so:
139 | ```nix
140 | {
141 | services.spacebar.enable = true;
142 | services.spacebar.package = pkgs.spacebar;
143 | services.spacebar.config = {
144 | position = "top";
145 | display = "main";
146 | height = 26;
147 | title = "on";
148 | spaces = "on";
149 | clock = "on";
150 | power = "on";
151 | padding_left = 20;
152 | padding_right = 20;
153 | spacing_left = 25;
154 | spacing_right = 15;
155 | text_font = ''"Menlo:Regular:12.0"'';
156 | icon_font = ''"Font Awesome 5 Free:Solid:12.0"'';
157 | background_color = "0xff202020";
158 | foreground_color = "0xffa8a8a8";
159 | power_icon_color = "0xffcd950c";
160 | battery_icon_color = "0xffd75f5f";
161 | dnd_icon_color = "0xffa8a8a8";
162 | clock_icon_color = "0xffa8a8a8";
163 | power_icon_strip = " ";
164 | space_icon = "•";
165 | space_icon_strip = "1 2 3 4 5 6 7 8 9 10";
166 | spaces_for_all_displays = "on";
167 | display_separator = "on";
168 | display_separator_icon = "";
169 | space_icon_color = "0xff458588";
170 | space_icon_color_secondary = "0xff78c4d4";
171 | space_icon_color_tertiary = "0xfffff9b0";
172 | clock_icon = "";
173 | dnd_icon = "";
174 | clock_format = ''"%d/%m/%y %R"'';
175 | right_shell = "on";
176 | right_shell_icon = "";
177 | right_shell_command = "whoami";
178 | };
179 | }
180 | ```
181 |
182 | ## Integration with yabai
183 | yabai provides the `external_bar` config option. This can be used so yabai plays nice with spacebar.
184 | Take a look at this excerpt from the yabai man page
185 | > external_bar [::]
186 | > Specify top and bottom padding for a potential custom bar that you may be running.
187 | > main: Apply the given padding only to spaces located on the main display.
188 | > all: Apply the given padding to all spaces regardless of their display.
189 | > off: Do not apply any special padding.
190 |
191 |
192 | So, if you like having spacebar at the bottom, you'd use `yabai -m config external_bar all:0:26`
193 |
194 | You can also use the command `spacebar -m config height` with no argument to get the current height, which you could then use in conjunction with `external_bar`:
195 | ```
196 | SPACEBAR_HEIGHT=$(spacebar -m config height)
197 | yabai -m config external_bar all:0:$SPACEBAR_HEIGHT
198 | ```
199 |
200 | ## Debug output and error reporting
201 | In the case that something isn't working as you're expecting, please make sure to take a look in the output and error log. To enable debug output make sure that your configuration file contains `spacebar -m config debug_output on` or that spacebar is launched with the `--verbose` flag.
202 |
203 | ### Homebrew
204 | If you're using the Homebrew service, the log files can be found in the following directory:
205 | ```
206 | # directory containing log files (HOMEBREW_PREFIX defaults to /usr/local unless you manually specified otherwise)
207 | $HOMEBREW_PREFIX/var/log/spacebar/
208 |
209 | # view the last lines of the error log
210 | tail -f /usr/local/var/log/spacebar/spacebar.err.log
211 |
212 | # view the last lines of the debug log
213 | tail -f /usr/local/var/log/spacebar/spacebar.out.log
214 | ```
215 |
216 | ### Nix
217 | If you're using the Nix service, you can set up debugging like so:
218 | ```nix
219 | {
220 | services.spacebar.config.debug_output = "on";
221 | launchd.user.agents.spacebar.serviceConfig.StandardErrorPath = "/tmp/spacebar.err.log";
222 | launchd.user.agents.spacebar.serviceConfig.StandardOutPath = "/tmp/spacebar.out.log";
223 | }
224 | ```
225 |
226 | ## Upgrading
227 | To upgrade the Homebrew package, run
228 | ```
229 | brew services stop spacebar
230 | brew upgrade spacebar
231 | brew services start spacebar
232 | ```
233 |
234 | If you're using the Nix package form the nixpkgs collection and keeping your channels up to date, package upgrades will roll in as you command.
235 | If you're using the Nix Flake, you can update your `input.spacebar.url` to point to the latest release tag and update your lockfile.
236 |
237 | ## Requirements and Caveats
238 | Please read the below requirements carefully.
239 | Make sure you fulfil all of them before filing an issue.
240 |
241 | |Requirement|Note|
242 | |-:|:-|
243 | |Operating System|macOS Catalina 10.15.0+ is supported.|
244 | |Accessibility API|spacebar must be given permission to utilize the Accessibility API and will request access upon launch. The application must be restarted after access has been granted.|
245 |
246 | Please also take note of the following caveats.
247 |
248 | |Caveat|Note|
249 | |-:|:-|
250 | |Code Signing|When building from source (or installing from HEAD), it is recommended to codesign the binary so it retains its accessibility and automation privileges when updated or rebuilt.|
251 | |Mission Control|In the Mission Control preferences pane in System Preferences, the setting "Automatically rearrange Spaces based on most recent use" should be disabled.|
252 |
253 | ## Releases and branches
254 | Main work for this project is conducted on the `master` branch, and thus it should be considered unstable (expect bugs!).
255 | There is no particular release cycle, just as and when features/fixes are ready :)
256 |
257 | ## License and Attributions
258 | spacebar is licensed under the [MIT License](LICENSE.txt), a short and simple permissive license with conditions only requiring preservation of copyright and license notices.
259 | Licensed works, modifications, and larger works may be distributed under different terms and without source code.
260 |
261 | Many thanks to [@koekeishiya](https://github.com/koekeishiya) for creating yabai, and providing the codebase for an example status bar, from which this project was born.
262 |
263 | ## Disclaimer
264 | Use at your own discretion.
265 | I take no responsibility if anything should happen to your machine while trying to install, test or otherwise use this software in any form.
266 |
--------------------------------------------------------------------------------
/doc/spacebar.1:
--------------------------------------------------------------------------------
1 | '\" t
2 | .\" Title: spacebar
3 | .\" Author: [see the "AUTHOR(S)" section]
4 | .\" Generator: Asciidoctor 2.0.10
5 | .\" Date: 2021-04-08
6 | .\" Manual: spacebar manual
7 | .\" Source: spacebar
8 | .\" Language: English
9 | .\"
10 | .TH "SPACEBAR" "1" "2021-04-08" "spacebar" "spacebar manual"
11 | .ie \n(.g .ds Aq \(aq
12 | .el .ds Aq '
13 | .ss \n[.ss] 0
14 | .nh
15 | .ad l
16 | .de URL
17 | \fI\\$2\fP <\\$1>\\$3
18 | ..
19 | .als MTO URL
20 | .if \n[.g] \{\
21 | . mso www.tmac
22 | . am URL
23 | . ad l
24 | . .
25 | . am MTO
26 | . ad l
27 | . .
28 | . LINKSTYLE blue R < >
29 | .\}
30 | .SH "NAME"
31 | spacebar \- a simple status bar for macOS
32 | .SH "SYNOPSIS"
33 | .sp
34 | \fBspacebar\fP [\fB\-v\fP,\fB\-\-version\fP|\fB\-V\fP,\fB\-\-verbose\fP|\fB\-m\fP,\fB\-\-message\fP \fImsg\fP|\fB\-c\fP,\fB\-\-config\fP \fIconfig_file\fP]
35 | .SH "DESCRIPTION"
36 | .sp
37 | \fBspacebar\fP is a simple status bar for macOS.
38 | .SH "OPTIONS"
39 | .sp
40 | \fB\-v\fP, \fB\-\-version\fP
41 | .RS 4
42 | Print the version and exit.
43 | .RE
44 | .sp
45 | \fB\-V\fP, \fB\-\-verbose\fP
46 | .RS 4
47 | Output debug information to stdout.
48 | .RE
49 | .sp
50 | \fB\-m\fP, \fB\-\-message\fP \fI\fP
51 | .RS 4
52 | Send message to a running instance of spacebar.
53 | .RE
54 | .sp
55 | \fB\-c\fP, \fB\-\-config\fP \fI\fP
56 | .RS 4
57 | Use the specified configuration file.
58 | .RE
59 | .SH "CONFIG"
60 | .SS "General Syntax"
61 | .sp
62 | spacebar \-m config
63 | .RS 4
64 | Get or set the value of .
65 | .RE
66 | .SS "Settings"
67 | .sp
68 | \fBdebug_output\fP [\fI\fP]
69 | .RS 4
70 | Enable output of debug information to stdout.
71 | .RE
72 | .sp
73 | \fBdisplay\fP [\fI\fP]
74 | .RS 4
75 | Display to draw the bar on.
76 | .br
77 | Must be one of \fImain\fP or \fIall\fP.
78 | .RE
79 | .sp
80 | \fBposition\fP [\fI\fP]
81 | .RS 4
82 | Position on the screen to draw the bar.
83 | .br
84 | Must be one of \fItop\fP or \fIbottom\fP.
85 | .RE
86 | .sp
87 | \fBheight\fP [\fI\fP]
88 | .RS 4
89 | Height in pixels to draw the bar.
90 | .RE
91 | .sp
92 | \fBtitle\fP [\fI\fP]
93 | .RS 4
94 | Enable focused window title display at the centre of the bar.
95 | .br
96 | This cannot be enabled when \fIcenter_shell\(cqis set to \(aqon\fP.
97 | .RE
98 | .sp
99 | \fBspaces\fP [\fI\fP]
100 | .RS 4
101 | Enable space indicator on the left of the bar.
102 | .RE
103 | .sp
104 | \fBspace_icon_strip\fP [\fI \fP]
105 | .RS 4
106 | Specify symbols separated by whitespace to be used for visualizing spaces.
107 | .RE
108 | .sp
109 | \fBspaces_for_all_displays\fP [\fI\fP]
110 | .RS 4
111 | Enable space indicators for spaces on each display.
112 | .RE
113 | .sp
114 | \fBspace_icon\fP [\fI\fP]
115 | .RS 4
116 | Specify a general symbol to use for any given space that does not have a match in \fIspace_icon_strip\fP.
117 | .RE
118 | .sp
119 | \fBspace_icon_color\fP [\fI\fP]
120 | .RS 4
121 | Color to use for drawing the current space icon.
122 | .RE
123 | .sp
124 | \fBspace_icon_color_secondary\fP [\fI\fP]
125 | .RS 4
126 | Color to use for drawing the current space icon of the second display.
127 | .RE
128 | .sp
129 | \fBspace_icon_color_tertiary\fP [\fI\fP]
130 | .RS 4
131 | Color to use for drawing the current space icon of the third display.
132 | .RE
133 | .sp
134 | \fBdisplay_separator\fP [\fI\fP]
135 | .RS 4
136 | Enable separator between the spaces for each display.
137 | .RE
138 | .sp
139 | \fBdisplay_separator_icon\fP [\fI\fP]
140 | .RS 4
141 | Specify a symbol to use for the display separator.
142 | .RE
143 | .sp
144 | \fBdisplay_separator_icon_color\fP [\fI\fP]
145 | .RS 4
146 | Color to use for drawing the display separator icon.
147 | .RE
148 | .sp
149 | \fBclock\fP [\fI\fP]
150 | .RS 4
151 | Enable clock on the right of the bar.
152 | .RE
153 | .sp
154 | \fBpower\fP [\fI\fP]
155 | .RS 4
156 | Enable power indicator on the right of the bar.
157 | .RE
158 | .sp
159 | \fBpadding_left\fP [\fI\fP]
160 | .RS 4
161 | Padding in pixels between the left edge of the display and the first item.
162 | .RE
163 | .sp
164 | \fBpadding_right\fP [\fI\fP]
165 | .RS 4
166 | Padding in pixels between the right edge of the display and the last item.
167 | .RE
168 | .sp
169 | \fBspacing_left\fP [\fI\fP]
170 | .RS 4
171 | Spacing in pixels between the left space indicators.
172 | .RE
173 | .sp
174 | \fBspacing_right\fP [\fI\fP]
175 | .RS 4
176 | Spacing in pixels between the right status segments.
177 | .RE
178 | .sp
179 | \fBtext_font\fP [\fI::\fP]
180 | .RS 4
181 | Specify name, style and size of font to use for drawing text.
182 | .br
183 | Use \fIFont Book.app\fP to identify the correct name.
184 | .RE
185 | .sp
186 | \fBicon_font\fP [\fI::\fP]
187 | .RS 4
188 | Specify name, style and size of font to use for drawing icon symbols.
189 | .br
190 | Use \fIFont Book.app\fP to identify the correct name.
191 | .RE
192 | .sp
193 | \fBbackground_color\fP [\fI\fP]
194 | .RS 4
195 | Color to use for drawing status bar background.
196 | .RE
197 | .sp
198 | \fBforeground_color\fP [\fI\fP]
199 | .RS 4
200 | Color to use for drawing status bar elements.
201 | .RE
202 | .sp
203 | \fBpower_icon_strip\fP [\fI \fP]
204 | .RS 4
205 | Specify two symbols separated by whitespace.
206 | .br
207 | The first symbol represents battery power and the second symbol indicates AC.
208 | .RE
209 | .sp
210 | \fBpower_icon_color\fP [\fI\fP]
211 | .RS 4
212 | Color to use for drawing the power (charging) icon.
213 | .RE
214 | .sp
215 | \fBbattery_icon_color\fP [\fI\fP]
216 | .RS 4
217 | Color to use for drawing the battery icon.
218 | .RE
219 | .sp
220 | \fBclock_icon\fP [\fI\fP]
221 | .RS 4
222 | Specify a symbol to represent the current time.
223 | .RE
224 | .sp
225 | \fBclock_icon_color\fP [\fI\fP]
226 | .RS 4
227 | Color to use for drawing the clock icon.
228 | .RE
229 | .sp
230 | \fBclock_format\fP [\fI\fP]
231 | .RS 4
232 | Specify a format for the current time, according to the strftime function.
233 | .RE
234 | .sp
235 | \fBdnd\fP [\fI\fP]
236 | .RS 4
237 | Enable a DoNotDisturb indicator on the right of the bar.
238 | .RE
239 | .sp
240 | \fBdnd_icon\fP [\fI\fP]
241 | .RS 4
242 | Specify a symbol to represent the current DoNotDisturb status.
243 | .RE
244 | .sp
245 | \fBdnd_icon_color\fP [\fI\fP]
246 | .RS 4
247 | Color to use for drawing the DoNotDisturb icon.
248 | .RE
249 | .sp
250 | \fBleft_shell\fP [\fI\fP]
251 | .RS 4
252 | Enable shell output on the left of the bar.
253 | .RE
254 | .sp
255 | \fBright_shell\fP [\fI\fP]
256 | .RS 4
257 | Enable shell output on the right of the bar.
258 | .RE
259 | .sp
260 | \fBcenter_shell\fP [\fI\fP]
261 | .RS 4
262 | Enable shell output at the center of the bar.
263 | .br
264 | This cannot be enabled when \fItitle\fP is set to \fIon\fP.
265 | .RE
266 | .sp
267 | \fBleft_shell_icon\fP [\fI\fP]
268 | .RS 4
269 | Specify a symbol to prefix the left shell output.
270 | .RE
271 | .sp
272 | \fBleft_shell_icon_color\fP [\fI\fP]
273 | .RS 4
274 | Color to use for drawing the left shell icon.
275 | .RE
276 | .sp
277 | \fBleft_shell_command\fP [\fI\fP]
278 | .RS 4
279 | Command pipeline to retrieve the output for displaying in the left shell section.
280 | .br
281 | There is NO timeout protection for the command pipeline, so be sure to set it to something that returns output quickly.
282 | .RE
283 | .sp
284 | \fBright_shell_icon\fP [\fI\fP]
285 | .RS 4
286 | Specify a symbol to prefix the right shell output.
287 | .RE
288 | .sp
289 | \fBright_shell_icon_color\fP [\fI\fP]
290 | .RS 4
291 | Color to use for drawing the right shell icon.
292 | .RE
293 | .sp
294 | \fBright_shell_command\fP [\fI\fP]
295 | .RS 4
296 | Command pipeline to retrieve the output for displaying in the right shell section.
297 | .br
298 | There is NO timeout protection for the command pipeline, so be sure to set it to something that returns output quickly.
299 | .RE
300 | .sp
301 | \fBcenter_shell_command\fP [\fI\fP]
302 | .RS 4
303 | Command pipeline to retrieve the output for displaying in the center shell section.
304 | .br
305 | There is NO timeout protection for the command pipeline, so be sure to set it to something that returns output quickly.
306 | .br
307 | .RE
308 | .SH "EXIT CODES"
309 | .sp
310 | If \fBspacebar\fP can\(cqt handle a message, it will return a non\-zero exit code.
311 | .SH "AUTHOR"
312 | .sp
313 | Calum MacRae
--------------------------------------------------------------------------------
/doc/spacebar.asciidoc:
--------------------------------------------------------------------------------
1 | :man source: spacebar
2 | :man version: {revnumber}
3 | :man manual: spacebar manual
4 |
5 | ifdef::env-github[]
6 | :toc:
7 | :toc-title:
8 | :toc-placement!:
9 | :numbered:
10 | endif::[]
11 |
12 | spacebar(1)
13 | ===========
14 |
15 | ifdef::env-github[]
16 | toc::[]
17 | endif::[]
18 |
19 | Name
20 | ----
21 |
22 | spacebar - a simple status bar for macOS
23 |
24 | Synopsis
25 | --------
26 |
27 | *spacebar* [*-v*,*--version*|*-V*,*--verbose*|*-m*,*--message* 'msg'|*-c*,*--config* 'config_file']
28 |
29 | Description
30 | -----------
31 |
32 | *spacebar* is a simple status bar for macOS.
33 |
34 | Options
35 | -------
36 | *-v*, *--version*::
37 | Print the version and exit.
38 |
39 | *-V*, *--verbose*::
40 | Output debug information to stdout.
41 |
42 | *-m*, *--message* ''::
43 | Send message to a running instance of spacebar.
44 |
45 | *-c*, *--config* ''::
46 | Use the specified configuration file.
47 |
48 | Config
49 | ------
50 |
51 | General Syntax
52 | ~~~~~~~~~~~~~~
53 |
54 | spacebar -m config ::
55 | Get or set the value of .
56 |
57 | Settings
58 | ~~~~~~~~
59 |
60 | *debug_output* ['']::
61 | Enable output of debug information to stdout.
62 |
63 | *display* ['']::
64 | Display to draw the bar on. +
65 | Must be one of 'main' or 'all'.
66 |
67 | *position* ['']::
68 | Position on the screen to draw the bar. +
69 | Must be one of 'top' or 'bottom'.
70 |
71 | *height* ['']::
72 | Height in pixels to draw the bar.
73 |
74 | *title* ['']::
75 | Enable focused window title display at the centre of the bar. +
76 | This cannot be enabled when 'center_shell'is set to 'on'.
77 |
78 | *spaces* ['']::
79 | Enable space indicator on the left of the bar.
80 |
81 | *space_icon_strip* [' ']::
82 | Specify symbols separated by whitespace to be used for visualizing spaces.
83 |
84 | *spaces_for_all_displays* ['']::
85 | Enable space indicators for spaces on each display.
86 |
87 | *space_icon* ['']::
88 | Specify a general symbol to use for any given space that does not have a match in 'space_icon_strip'.
89 |
90 | *space_icon_color* ['']::
91 | Color to use for drawing the current space icon.
92 |
93 | *space_icon_color_secondary* ['']::
94 | Color to use for drawing the current space icon of the second display.
95 |
96 | *space_icon_color_tertiary* ['']::
97 | Color to use for drawing the current space icon of the third display.
98 |
99 | *display_separator* ['']::
100 | Enable separator between the spaces for each display.
101 |
102 | *display_separator_icon* ['']::
103 | Specify a symbol to use for the display separator.
104 |
105 | *display_separator_icon_color* ['']::
106 | Color to use for drawing the display separator icon.
107 |
108 | *clock* ['']::
109 | Enable clock on the right of the bar.
110 |
111 | *power* ['']::
112 | Enable power indicator on the right of the bar.
113 |
114 | *padding_left* ['']::
115 | Padding in pixels between the left edge of the display and the first item.
116 |
117 | *padding_right* ['']::
118 | Padding in pixels between the right edge of the display and the last item.
119 |
120 | *spacing_left* ['']::
121 | Spacing in pixels between the left space indicators.
122 |
123 | *spacing_right* ['']::
124 | Spacing in pixels between the right status segments.
125 |
126 | *text_font* ['::']::
127 | Specify name, style and size of font to use for drawing text. +
128 | Use 'Font Book.app' to identify the correct name.
129 |
130 | *icon_font* ['::']::
131 | Specify name, style and size of font to use for drawing icon symbols. +
132 | Use 'Font Book.app' to identify the correct name.
133 |
134 | *background_color* ['']::
135 | Color to use for drawing status bar background.
136 |
137 | *foreground_color* ['']::
138 | Color to use for drawing status bar elements.
139 |
140 | *power_icon_strip* [' ']::
141 | Specify two symbols separated by whitespace. +
142 | The first symbol represents battery power and the second symbol indicates AC.
143 |
144 | *power_icon_color* ['']::
145 | Color to use for drawing the power (charging) icon.
146 |
147 | *battery_icon_color* ['']::
148 | Color to use for drawing the battery icon.
149 |
150 | *clock_icon* ['']::
151 | Specify a symbol to represent the current time.
152 |
153 | *clock_icon_color* ['']::
154 | Color to use for drawing the clock icon.
155 |
156 | *clock_format* ['']::
157 | Specify a format for the current time, according to the strftime function.
158 |
159 | *dnd* ['']::
160 | Enable a DoNotDisturb indicator on the right of the bar.
161 |
162 | *dnd_icon* ['']::
163 | Specify a symbol to represent the current DoNotDisturb status.
164 |
165 | *dnd_icon_color* ['']::
166 | Color to use for drawing the DoNotDisturb icon.
167 |
168 | *left_shell* ['']::
169 | Enable shell output on the left of the bar.
170 |
171 | *right_shell* ['']::
172 | Enable shell output on the right of the bar.
173 |
174 | *center_shell* ['']::
175 | Enable shell output at the center of the bar. +
176 | This cannot be enabled when 'title' is set to 'on'.
177 |
178 | *left_shell_icon* ['']::
179 | Specify a symbol to prefix the left shell output.
180 |
181 | *left_shell_icon_color* ['']::
182 | Color to use for drawing the left shell icon.
183 |
184 | *left_shell_command* ['']::
185 | Command pipeline to retrieve the output for displaying in the left shell section. +
186 | There is NO timeout protection for the command pipeline, so be sure to set it to something that returns output quickly.
187 |
188 | *right_shell_icon* ['']::
189 | Specify a symbol to prefix the right shell output.
190 |
191 | *right_shell_icon_color* ['']::
192 | Color to use for drawing the right shell icon.
193 |
194 | *right_shell_command* ['']::
195 | Command pipeline to retrieve the output for displaying in the right shell section. +
196 | There is NO timeout protection for the command pipeline, so be sure to set it to something that returns output quickly.
197 |
198 | *center_shell_command* ['']::
199 | Command pipeline to retrieve the output for displaying in the center shell section. +
200 | There is NO timeout protection for the command pipeline, so be sure to set it to something that returns output quickly. +
201 |
202 |
203 | Exit Codes
204 | ----------
205 |
206 | If *spacebar* can't handle a message, it will return a non-zero exit code.
207 |
208 | Author
209 | ------
210 |
211 | Calum MacRae
212 |
--------------------------------------------------------------------------------
/examples/spacebarrc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | spacebar -m config position top
4 | spacebar -m config display main
5 | spacebar -m config height 26
6 | spacebar -m config title on
7 | spacebar -m config spaces on
8 | spacebar -m config clock on
9 | spacebar -m config power on
10 | spacebar -m config padding_left 20
11 | spacebar -m config padding_right 20
12 | spacebar -m config spacing_left 25
13 | spacebar -m config spacing_right 15
14 | spacebar -m config text_font "Menlo:Regular:12.0"
15 | spacebar -m config icon_font "Font Awesome 5 Free:Solid:12.0"
16 | spacebar -m config background_color 0xff202020
17 | spacebar -m config foreground_color 0xffa8a8a8
18 | spacebar -m config power_icon_color 0xffcd950c
19 | spacebar -m config battery_icon_color 0xffd75f5f
20 | spacebar -m config dnd_icon_color 0xffa8a8a8
21 | spacebar -m config clock_icon_color 0xffa8a8a8
22 | spacebar -m config power_icon_strip
23 | spacebar -m config space_icon •
24 | spacebar -m config space_icon_color 0xffffab91
25 | spacebar -m config space_icon_color_secondary 0xff78c4d4
26 | spacebar -m config space_icon_color_tertiary 0xfffff9b0
27 | spacebar -m config space_icon_strip 1 2 3 4 5 6 7 8 9 10
28 | spacebar -m config spaces_for_all_displays on
29 | spacebar -m config clock_icon
30 | spacebar -m config dnd_icon
31 | spacebar -m config clock_format "%d/%m/%y %R"
32 | spacebar -m config right_shell on
33 | spacebar -m config right_shell_icon
34 | spacebar -m config right_shell_command "whoami"
35 |
36 | echo "spacebar configuration loaded.."
37 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-utils": {
4 | "locked": {
5 | "lastModified": 1644229661,
6 | "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
7 | "owner": "numtide",
8 | "repo": "flake-utils",
9 | "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "numtide",
14 | "repo": "flake-utils",
15 | "type": "github"
16 | }
17 | },
18 | "nixpkgs": {
19 | "locked": {
20 | "lastModified": 1638239011,
21 | "narHash": "sha256-AjhmbT4UBlJWqxY0ea8a6GU2C2HdKUREkG43oRr3TZg=",
22 | "owner": "NixOS",
23 | "repo": "nixpkgs",
24 | "rev": "a7ecde854aee5c4c7cd6177f54a99d2c1ff28a31",
25 | "type": "github"
26 | },
27 | "original": {
28 | "owner": "NixOS",
29 | "ref": "21.11",
30 | "repo": "nixpkgs",
31 | "type": "github"
32 | }
33 | },
34 | "root": {
35 | "inputs": {
36 | "flake-utils": "flake-utils",
37 | "nixpkgs": "nixpkgs"
38 | }
39 | }
40 | },
41 | "root": "root",
42 | "version": 7
43 | }
44 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "A minimal status bar for macOS";
3 |
4 | inputs.nixpkgs.url = github:NixOS/nixpkgs/21.11;
5 | inputs.flake-utils.url = github:numtide/flake-utils;
6 |
7 | outputs = { self, nixpkgs, flake-utils }:
8 | {
9 | overlay = final: prev: { inherit (self.packages.${final.system}) spacebar; };
10 | }
11 | // flake-utils.lib.eachSystem [ "aarch64-darwin" "x86_64-darwin" ] (system:
12 | let pkgs = nixpkgs.legacyPackages.${system}; in
13 | rec {
14 | packages = flake-utils.lib.flattenTree {
15 | spacebar = pkgs.stdenv.mkDerivation rec {
16 | pname = "spacebar";
17 | version = "1.4.0";
18 | src = self;
19 |
20 | buildInputs = with pkgs.darwin.apple_sdk.frameworks; [
21 | Carbon
22 | Cocoa
23 | ScriptingBridge
24 | SkyLight
25 | ];
26 |
27 | installPhase = ''
28 | mkdir -p $out/bin
29 | mkdir -p $out/share/man/man1/
30 | cp ./bin/spacebar $out/bin/spacebar
31 | cp ./doc/spacebar.1 $out/share/man/man1/spacebar.1
32 | '';
33 |
34 | meta = with pkgs.lib; {
35 | description = "A minimal status bar for macOS";
36 | homepage = "https://github.com/cmacrae/spacebar";
37 | platforms = platforms.darwin;
38 | maintainers = [ maintainers.cmacrae ];
39 | license = licenses.mit;
40 | };
41 | };
42 | };
43 |
44 | defaultPackage = packages.spacebar;
45 | apps.spacebar = flake-utils.lib.mkApp { drv = packages.spacebar; };
46 | defaultApp = apps.spacebar;
47 |
48 | devShell = pkgs.mkShell {
49 | name = "spacebar";
50 | inputsFrom = [ packages.spacebar ];
51 | buildInputs = [ pkgs.asciidoctor ];
52 | };
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | FRAMEWORK_PATH = -F/System/Library/PrivateFrameworks
2 | FRAMEWORK = -framework Carbon -framework Cocoa -framework CoreServices -framework SkyLight -framework ScriptingBridge -framework IOKit
3 | BUILD_FLAGS = -std=c99 -Wall -DDEBUG -g -O0 -fvisibility=hidden -mmacosx-version-min=10.13
4 | BUILD_PATH = ./bin
5 | DOC_PATH = ./doc
6 | SMP_PATH = ./examples
7 | SPACEBAR_SRC = ./src/manifest.m
8 | BINS = $(BUILD_PATH)/spacebar
9 |
10 | .PHONY: all clean install man
11 |
12 | all: clean $(BINS)
13 |
14 | install: BUILD_FLAGS=-std=c99 -Wall -DNDEBUG -O2 -fvisibility=hidden -mmacosx-version-min=10.13
15 | install: clean $(BINS)
16 |
17 | stats: BUILD_FLAGS=-std=c99 -Wall -DSTATS -DNDEBUG -O2 -fvisibility=hidden -mmacosx-version-min=10.13
18 | stats: clean $(BINS)
19 |
20 | man:
21 | asciidoctor -b manpage $(DOC_PATH)/spacebar.asciidoc -o $(DOC_PATH)/spacebar.1 && \
22 | sed -i 's/1980-01-01/$(shell date "+%Y-%m-%d")/g' $(DOC_PATH)/spacebar.1
23 |
24 | clean:
25 | rm -rf $(BUILD_PATH)
26 |
27 | $(BUILD_PATH)/spacebar: $(SPACEBAR_SRC)
28 | mkdir -p $(BUILD_PATH)
29 | clang $^ $(BUILD_FLAGS) $(FRAMEWORK_PATH) $(FRAMEWORK) -o $@
30 |
--------------------------------------------------------------------------------
/src/application.c:
--------------------------------------------------------------------------------
1 | #include "application.h"
2 |
3 | extern struct event_loop g_event_loop;
4 |
5 | static OBSERVER_CALLBACK(application_notification_handler)
6 | {
7 | if (CFEqual(notification, kAXFocusedWindowChangedNotification)) {
8 | uint32_t window_id = ax_window_id(element);
9 | if (!window_id) return;
10 |
11 | struct event *event = event_create(&g_event_loop, WINDOW_FOCUSED, (void *)(intptr_t) window_id);
12 | event_loop_post(&g_event_loop, event);
13 | } else if (CFEqual(notification, kAXTitleChangedNotification)) {
14 | uint32_t window_id = ax_window_id(element);
15 | if (!window_id) return;
16 |
17 | struct event *event = event_create(&g_event_loop, WINDOW_TITLE_CHANGED, (void *)(intptr_t) window_id);
18 | event_loop_post(&g_event_loop, event);
19 | }
20 | }
21 |
22 | static void
23 | application_observe_notification(struct application *application, int notification)
24 | {
25 | AXError result = AXObserverAddNotification(application->observer_ref, application->ref, ax_application_notification[notification], application);
26 | if (result == kAXErrorSuccess || result == kAXErrorNotificationAlreadyRegistered) {
27 | application->notification |= 1 << notification;
28 | } else if (result != kAXErrorNotImplemented) {
29 | application->retry = true;
30 | }
31 | }
32 |
33 | static void
34 | application_unobserve_notification(struct application *application, int notification)
35 | {
36 | AXObserverRemoveNotification(application->observer_ref, application->ref, ax_application_notification[notification]);
37 | application->notification &= ~(1 << notification);
38 | }
39 |
40 | bool application_observe(struct application *application)
41 | {
42 | if (AXObserverCreate(application->pid, application_notification_handler, &application->observer_ref) == kAXErrorSuccess) {
43 | for (int i = 0; i < array_count(ax_application_notification); ++i) {
44 | application_observe_notification(application, i);
45 | }
46 |
47 | application->is_observing = true;
48 | CFRunLoopAddSource(CFRunLoopGetMain(), AXObserverGetRunLoopSource(application->observer_ref), kCFRunLoopDefaultMode);
49 | }
50 |
51 | return (application->notification & AX_APPLICATION_ALL) == AX_APPLICATION_ALL;
52 | }
53 |
54 | void application_unobserve(struct application *application)
55 | {
56 | if (application->is_observing) {
57 | for (int i = 0; i < array_count(ax_application_notification); ++i) {
58 | if (!(application->notification & (1 << i))) continue;
59 | application_unobserve_notification(application, i);
60 | }
61 |
62 | application->is_observing = false;
63 | CFRunLoopSourceInvalidate(AXObserverGetRunLoopSource(application->observer_ref));
64 | CFRelease(application->observer_ref);
65 | }
66 | }
67 |
68 | uint32_t application_focused_window(struct application *application)
69 | {
70 | CFTypeRef window_ref = NULL;
71 | AXUIElementCopyAttributeValue(application->ref, kAXFocusedWindowAttribute, &window_ref);
72 | if (!window_ref) return 0;
73 |
74 | uint32_t window_id = ax_window_id(window_ref);
75 | CFRelease(window_ref);
76 |
77 | return window_id;
78 | }
79 |
80 | struct application *application_create(struct process *process)
81 | {
82 | struct application *application = malloc(sizeof(struct application));
83 | memset(application, 0, sizeof(struct application));
84 | application->ref = AXUIElementCreateApplication(process->pid);
85 | application->psn = process->psn;
86 | application->pid = process->pid;
87 | application->name = process->name;
88 | return application;
89 | }
90 |
91 | void application_destroy(struct application *application)
92 | {
93 | CFRelease(application->ref);
94 | free(application);
95 | }
96 |
--------------------------------------------------------------------------------
/src/application.h:
--------------------------------------------------------------------------------
1 | #ifndef APPLICATION_H
2 | #define APPLICATION_H
3 |
4 | #define OBSERVER_CALLBACK(name) void name(AXObserverRef observer, AXUIElementRef element, CFStringRef notification, void *context)
5 | typedef OBSERVER_CALLBACK(observer_callback);
6 |
7 | #define AX_APPLICATION_WINDOW_FOCUSED_INDEX 0
8 | #define AX_APPLICATION_WINDOW_TITLE_CHANGED_INDEX 1
9 |
10 | #define AX_APPLICATION_WINDOW_FOCUSED (1 << AX_APPLICATION_WINDOW_FOCUSED_INDEX)
11 | #define AX_APPLICATION_WINDOW_TITLE_CHANGED (1 << AX_APPLICATION_WINDOW_TITLE_CHANGED_INDEX)
12 | #define AX_APPLICATION_ALL (AX_APPLICATION_WINDOW_FOCUSED |\
13 | AX_APPLICATION_WINDOW_TITLE_CHANGED)
14 | static CFStringRef ax_application_notification[] =
15 | {
16 | [AX_APPLICATION_WINDOW_FOCUSED_INDEX] = kAXFocusedWindowChangedNotification,
17 | [AX_APPLICATION_WINDOW_TITLE_CHANGED_INDEX] = kAXTitleChangedNotification,
18 | };
19 |
20 | struct application
21 | {
22 | AXUIElementRef ref;
23 | ProcessSerialNumber psn;
24 | uint32_t pid;
25 | char *name;
26 | AXObserverRef observer_ref;
27 | uint8_t notification;
28 | bool is_observing;
29 | bool retry;
30 | };
31 |
32 | uint32_t application_focused_window(struct application *application);
33 | bool application_observe(struct application *application);
34 | void application_unobserve(struct application *application);
35 | struct application *application_create(struct process *process);
36 | void application_destroy(struct application *application);
37 |
38 | #endif
39 |
--------------------------------------------------------------------------------
/src/application_manager.c:
--------------------------------------------------------------------------------
1 | #include "application_manager.h"
2 |
3 | extern struct process_manager g_process_manager;
4 | extern struct mouse_state g_mouse_state;
5 | extern char g_sa_socket_file[MAXLEN];
6 |
7 | static TABLE_HASH_FUNC(hash_application)
8 | {
9 | unsigned long result = *(uint32_t *) key;
10 | result = (result + 0x7ed55d16) + (result << 12);
11 | result = (result ^ 0xc761c23c) ^ (result >> 19);
12 | result = (result + 0x165667b1) + (result << 5);
13 | result = (result + 0xd3a2646c) ^ (result << 9);
14 | result = (result + 0xfd7046c5) + (result << 3);
15 | result = (result ^ 0xb55a4f09) ^ (result >> 16);
16 | return result;
17 | }
18 |
19 | static TABLE_COMPARE_FUNC(compare_application)
20 | {
21 | return *(uint32_t *) key_a == *(uint32_t *) key_b;
22 | }
23 |
24 | #pragma clang diagnostic push
25 | #pragma clang diagnostic ignored "-Wdeprecated-declarations"
26 | struct application *application_manager_focused_application(struct application_manager *application_manager)
27 | {
28 | ProcessSerialNumber psn = {};
29 | _SLPSGetFrontProcess(&psn);
30 |
31 | pid_t pid;
32 | GetProcessPID(&psn, &pid);
33 |
34 | return application_manager_find_application(application_manager, pid);
35 | }
36 | #pragma clang diagnostic pop
37 |
38 | struct application *application_manager_find_application(struct application_manager *application_manager, pid_t pid)
39 | {
40 | return table_find(&application_manager->application, &pid);
41 | }
42 |
43 | void application_manager_remove_application(struct application_manager *application_manager, pid_t pid)
44 | {
45 | table_remove(&application_manager->application, &pid);
46 | }
47 |
48 | void application_manager_add_application(struct application_manager *application_manager, struct application *application)
49 | {
50 | table_add(&application_manager->application, &application->pid, application);
51 | }
52 |
53 | void application_manager_init(struct application_manager *application_manager)
54 | {
55 | application_manager->system_element = AXUIElementCreateSystemWide();
56 | AXUIElementSetMessagingTimeout(application_manager->system_element, 1.0);
57 |
58 | table_init(&application_manager->application, 150, hash_application, compare_application);
59 | }
60 |
61 | void application_manager_begin(struct application_manager *application_manager)
62 | {
63 | for (int process_index = 0; process_index < g_process_manager.process.capacity; ++process_index) {
64 | struct bucket *bucket = g_process_manager.process.buckets[process_index];
65 | while (bucket) {
66 | if (bucket->value) {
67 | struct process *process = bucket->value;
68 | struct application *application = application_create(process);
69 |
70 | if (application_observe(application)) {
71 | application_manager_add_application(application_manager, application);
72 | } else {
73 | application_unobserve(application);
74 | application_destroy(application);
75 | }
76 | }
77 |
78 | bucket = bucket->next;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/application_manager.h:
--------------------------------------------------------------------------------
1 | #ifndef APPLICATION_MANAGER_H
2 | #define APPLICATION_MANAGER_H
3 |
4 | extern CFTypeRef SLSWindowQueryWindows(int cid, CFArrayRef windows, int count);
5 | extern CFTypeRef SLSWindowQueryResultCopyWindows(CFTypeRef window_query);
6 | extern CGError SLSWindowIteratorAdvance(CFTypeRef iterator);
7 | extern uint32_t SLSWindowIteratorGetParentID(CFTypeRef iterator);
8 | extern uint32_t SLSWindowIteratorGetWindowID(CFTypeRef iterator);
9 | extern OSStatus _SLPSGetFrontProcess(ProcessSerialNumber *psn);
10 | extern CGError SLSGetWindowOwner(int cid, uint32_t wid, int *wcid);
11 | extern CGError SLSGetConnectionPSN(int cid, ProcessSerialNumber *psn);
12 | extern CGError SLSConnectionGetPID(int cid, pid_t *pid);
13 | extern CGError _SLPSSetFrontProcessWithOptions(ProcessSerialNumber *psn, uint32_t wid, uint32_t mode);
14 | extern CGError SLPSPostEventRecordTo(ProcessSerialNumber *psn, uint8_t *bytes);
15 | extern OSStatus SLSFindWindowByGeometry(int cid, int zero, int one, int zero_again, CGPoint *screen_point, CGPoint *window_point, uint32_t *wid, int *wcid);
16 | extern CGError SLSGetCurrentCursorLocation(int cid, CGPoint *point);
17 |
18 | #define kCPSAllWindows 0x100
19 | #define kCPSUserGenerated 0x200
20 | #define kCPSNoWindows 0x400
21 |
22 | struct application_manager
23 | {
24 | AXUIElementRef system_element;
25 | struct table application;
26 | };
27 |
28 | struct application *application_manager_focused_application(struct application_manager *application_manager);
29 | struct application *application_manager_find_application(struct application_manager *application_manager, pid_t pid);
30 | void application_manager_remove_application(struct application_manager *application_manager, pid_t pid);
31 | void application_manager_add_application(struct application_manager *application_manager, struct application *application);
32 | void application_manager_begin(struct application_manager *application_manager);
33 | void application_manager_init(struct application_manager *application_manager);
34 |
35 | #endif
36 |
--------------------------------------------------------------------------------
/src/bar.c:
--------------------------------------------------------------------------------
1 | #include "bar.h"
2 |
3 | extern struct event_loop g_event_loop;
4 | extern struct bar_manager g_bar_manager;
5 |
6 | static POWER_CALLBACK(power_handler)
7 | {
8 | struct event *event = event_create(&g_event_loop, BAR_REFRESH, NULL);
9 | event_loop_post(&g_event_loop, event);
10 | }
11 |
12 | static TIMER_CALLBACK(timer_handler)
13 | {
14 | struct event *event = event_create(&g_event_loop, BAR_REFRESH, NULL);
15 | event_loop_post(&g_event_loop, event);
16 | }
17 |
18 | static SHELL_TIMER_CALLBACK(shell_timer_handler)
19 | {
20 | struct event *event = event_create(&g_event_loop, SHELL_REFRESH, NULL);
21 | event_loop_post(&g_event_loop, event);
22 | }
23 |
24 | static int bar_find_battery_life(bool *has_battery, bool *charging)
25 | {
26 | CFTypeRef ps_info = IOPSCopyPowerSourcesInfo();
27 | CFTypeRef ps_list = IOPSCopyPowerSourcesList(ps_info);
28 |
29 | int ps_count = CFArrayGetCount(ps_list);
30 | if (!ps_count) return 0;
31 |
32 | int cur_capacity = 0;
33 | int max_capacity = 0;
34 | int percent = 0;
35 |
36 | for (int i = 0; i < ps_count; ++i) {
37 | CFDictionaryRef ps = IOPSGetPowerSourceDescription(ps_info, CFArrayGetValueAtIndex(ps_list, i));
38 | if (!ps) continue;
39 |
40 | CFTypeRef ps_type = CFDictionaryGetValue(ps, CFSTR(kIOPSTypeKey));
41 | if (!ps_type || !CFEqual(ps_type, CFSTR(kIOPSInternalBatteryType))) continue;
42 |
43 | CFTypeRef ps_cur = CFDictionaryGetValue(ps, CFSTR(kIOPSCurrentCapacityKey));
44 | if (!ps_cur) continue;
45 |
46 | CFTypeRef ps_max = CFDictionaryGetValue(ps, CFSTR(kIOPSMaxCapacityKey));
47 | if (!ps_max) continue;
48 |
49 | CFTypeRef ps_charging = CFDictionaryGetValue(ps, CFSTR(kIOPSPowerSourceStateKey));
50 | if (!ps_charging) continue;
51 |
52 | CFNumberGetValue((CFNumberRef) ps_cur, kCFNumberSInt32Type, &cur_capacity);
53 | CFNumberGetValue((CFNumberRef) ps_max, kCFNumberSInt32Type, &max_capacity);
54 | *charging = !CFEqual(ps_charging, CFSTR(kIOPSBatteryPowerValue));
55 | *has_battery = true;
56 | percent = (int)((double) cur_capacity / (double) max_capacity * 100);
57 | break;
58 | }
59 |
60 | CFRelease(ps_list);
61 | CFRelease(ps_info);
62 | return percent;
63 | }
64 |
65 | static CTFontRef bar_create_font(char *cstring)
66 | {
67 | float size = 10.0f;
68 | char font_properties[2][255] = { {}, {} };
69 | sscanf(cstring, "%254[^:]:%254[^:]:%f", font_properties[0], font_properties[1], &size);
70 | CFStringRef font_family_name = CFStringCreateWithCString(NULL, font_properties[0], kCFStringEncodingUTF8);
71 | CFStringRef font_style_name = CFStringCreateWithCString(NULL, font_properties[1], kCFStringEncodingUTF8);
72 | CFNumberRef font_size = CFNumberCreate(NULL, kCFNumberFloat32Type, &size);
73 |
74 | const void *keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute, kCTFontSizeAttribute };
75 | const void *values[] = { font_family_name, font_style_name, font_size };
76 | CFDictionaryRef attributes = CFDictionaryCreate(NULL, keys, values, array_count(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
77 | CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes(attributes);
78 | CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL);
79 |
80 | CFRelease(descriptor);
81 | CFRelease(attributes);
82 | CFRelease(font_size);
83 | CFRelease(font_style_name);
84 | CFRelease(font_family_name);
85 |
86 | return font;
87 | }
88 |
89 | static CGPoint bar_align_line(struct bar *bar, struct bar_line line, int align_x, int align_y)
90 | {
91 | float x = 0, y = 0;
92 |
93 | if (align_x == ALIGN_NONE) {
94 | x = CGContextGetTextPosition(bar->context).x;
95 | } else if (align_x == ALIGN_LEFT) {
96 | x = 20;
97 | } else if (align_x == ALIGN_CENTER) {
98 | x = (bar->frame.size.width / 2) - (line.bounds.size.width / 2);
99 | } else if (align_x == ALIGN_RIGHT) {
100 | x = bar->frame.size.width - line.bounds.size.width - 20;
101 | }
102 |
103 | if (align_y == ALIGN_NONE) {
104 | y = CGContextGetTextPosition(bar->context).y;
105 | } else if (align_y == ALIGN_TOP) {
106 | y = bar->frame.size.height;
107 | } else if (align_y == ALIGN_CENTER) {
108 | y = (bar->frame.size.height / 2) - ((line.ascent - line.descent) / 2);
109 | } else if (align_y == ALIGN_BOTTOM) {
110 | y = line.descent;
111 | }
112 |
113 | return (CGPoint) { x, y };
114 | }
115 |
116 | static void bar_draw_line(struct bar *bar, struct bar_line line, float x, float y)
117 | {
118 | CGContextSetRGBFillColor(bar->context, line.color.r, line.color.g, line.color.b, line.color.a);
119 | CGContextSetTextPosition(bar->context, x, y);
120 | CTLineDraw(line.line, bar->context);
121 | }
122 |
123 | static void bar_destroy_line(struct bar_line line)
124 | {
125 | CFRelease(line.line);
126 | }
127 |
128 | static struct bar_line bar_prepare_line(CTFontRef font, char *cstring, struct rgba_color color)
129 | {
130 | const void *keys[] = { kCTFontAttributeName, kCTForegroundColorFromContextAttributeName };
131 | const void *values[] = { font, kCFBooleanTrue };
132 | CFDictionaryRef attributes = CFDictionaryCreate(NULL, keys, values, array_count(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
133 | CFStringRef string = CFStringCreateWithCString(NULL, cstring, kCFStringEncodingUTF8);
134 | CFAttributedStringRef attr_string = CFAttributedStringCreate(NULL, string, attributes);
135 | CTLineRef line = CTLineCreateWithAttributedString(attr_string);
136 |
137 | CGFloat ascent, descent;
138 | CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
139 | CGRect bounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsUseGlyphPathBounds);
140 |
141 | CFRelease(string);
142 | CFRelease(attributes);
143 | CFRelease(attr_string);
144 |
145 | return (struct bar_line) {
146 | .line = line,
147 | .ascent = ascent,
148 | .descent = descent,
149 | .bounds = bounds,
150 | .color = color
151 | };
152 | }
153 |
154 | #pragma clang diagnostic push
155 | #pragma clang diagnostic ignored "-Wdeprecated-declarations"
156 | static char * focused_window_title()
157 | {
158 | ProcessSerialNumber psn = {};
159 | _SLPSGetFrontProcess(&psn);
160 |
161 | pid_t pid;
162 | GetProcessPID(&psn, &pid);
163 |
164 | AXUIElementRef application_ref = AXUIElementCreateApplication(pid);
165 | if (!application_ref)
166 | return NULL;
167 |
168 | CFTypeRef window_ref = NULL;
169 | AXUIElementCopyAttributeValue(application_ref, kAXFocusedWindowAttribute, &window_ref);
170 | if (!window_ref) {
171 | CFRelease(application_ref);
172 | return NULL;
173 | }
174 |
175 | char *title = NULL;
176 | CFTypeRef value = NULL;
177 | AXUIElementCopyAttributeValue(window_ref, kAXTitleAttribute, &value);
178 | if (value) {
179 | title = cfstring_copy(value);
180 | CFRelease(value);
181 | }
182 |
183 | CFRelease(window_ref);
184 | CFRelease(application_ref);
185 |
186 | return title;
187 | }
188 | #pragma clang diagnostic pop
189 |
190 | static int mission_control_index(uint64_t sid)
191 | {
192 | uint64_t result = 0;
193 | int desktop_cnt = 1;
194 |
195 | CFArrayRef display_spaces_ref = SLSCopyManagedDisplaySpaces(g_connection);
196 | int display_spaces_count = CFArrayGetCount(display_spaces_ref);
197 |
198 | for (int i = 0; i < display_spaces_count; ++i) {
199 | CFDictionaryRef display_ref = CFArrayGetValueAtIndex(display_spaces_ref, i);
200 | CFArrayRef spaces_ref = CFDictionaryGetValue(display_ref, CFSTR("Spaces"));
201 | int spaces_count = CFArrayGetCount(spaces_ref);
202 |
203 | for (int j = 0; j < spaces_count; ++j) {
204 | CFDictionaryRef space_ref = CFArrayGetValueAtIndex(spaces_ref, j);
205 | CFNumberRef sid_ref = CFDictionaryGetValue(space_ref, CFSTR("id64"));
206 | CFNumberGetValue(sid_ref, CFNumberGetType(sid_ref), &result);
207 | if (sid == result) goto out;
208 |
209 | ++desktop_cnt;
210 | }
211 | }
212 |
213 | desktop_cnt = 0;
214 | out:
215 | CFRelease(display_spaces_ref);
216 | return desktop_cnt;
217 | }
218 |
219 | void bar_refresh(struct bar *bar)
220 | {
221 | SLSDisableUpdate(g_connection);
222 | SLSOrderWindow(g_connection, bar->id, -1, 0);
223 | CGContextClearRect(bar->context, bar->frame);
224 | CGContextSetRGBFillColor(bar->context, g_bar_manager.background_color.r, g_bar_manager.background_color.g, g_bar_manager.background_color.b, g_bar_manager.background_color.a);
225 | CGContextFillRect(bar->context, bar->frame);
226 | CGContextStrokePath(bar->context);
227 |
228 | //
229 | // BAR LEFT
230 | //
231 |
232 | int bar_left_final_item_x = g_bar_manager.padding_left;
233 | if (g_bar_manager.spaces) {
234 | int space_count;
235 |
236 | if (g_bar_manager.spaces_for_all_displays) {
237 | uint32_t display_count = display_manager_active_display_count();
238 | for (uint32_t d_index = 1; d_index <= display_count; d_index++)
239 | {
240 | uint32_t did = display_manager_arrangement_display_id(d_index);
241 | uint64_t *space_list = display_space_list(did, &space_count);
242 |
243 | uint64_t sid = display_space_id(did);
244 |
245 | for (int i = 0; i < space_count; ++i) {
246 | CGPoint pos = CGContextGetTextPosition(bar->context);
247 |
248 | int index = mission_control_index(space_list[i]) - 1;
249 |
250 | struct bar_line space_line = index >= buf_len(g_bar_manager.space_icon_strip)
251 | ? g_bar_manager.space_icon
252 | : g_bar_manager.space_icon_strip[index];
253 | if (i == 0) {
254 | pos = bar_align_line(bar, space_line, ALIGN_LEFT, ALIGN_CENTER);
255 | pos.x = bar_left_final_item_x;
256 | } else {
257 | pos.x += g_bar_manager.spacing_left;
258 | }
259 |
260 | bar_left_final_item_x = pos.x + g_bar_manager.spacing_left;
261 |
262 | if (sid == space_list[i]) {
263 | if (d_index == 1) {
264 | space_line.color = g_bar_manager.space_icon_color;
265 | } else if (d_index == 2) {
266 | space_line.color = g_bar_manager.space_icon_color_secondary;
267 | } else if (d_index == 3) {
268 | space_line.color = g_bar_manager.space_icon_color_tertiary;
269 | }
270 | }
271 |
272 | bar_draw_line(bar, space_line, pos.x, pos.y);
273 | }
274 |
275 | if ((d_index < display_count) && g_bar_manager.display_separator) {
276 | struct bar_line display_separator_icon = g_bar_manager.display_separator_icon;
277 | CGPoint s_pos = bar_align_line(bar, display_separator_icon, 0, ALIGN_CENTER);
278 | s_pos.x = bar_left_final_item_x;
279 | bar_left_final_item_x = s_pos.x + g_bar_manager.spacing_left;
280 | bar_draw_line(bar, display_separator_icon, s_pos.x, s_pos.y);
281 | }
282 |
283 | free(space_list);
284 | }
285 | } else {
286 | uint64_t *space_list = display_space_list(bar->did, &space_count);
287 | if (space_list) {
288 | uint64_t sid = display_space_id(bar->did);
289 |
290 | for (int i = 0; i < space_count; ++i) {
291 | CGPoint pos = CGContextGetTextPosition(bar->context);
292 |
293 | int index = mission_control_index(space_list[i]) - 1;
294 |
295 | struct bar_line space_line = index >= buf_len(g_bar_manager.space_icon_strip)
296 | ? g_bar_manager.space_icon
297 | : g_bar_manager.space_icon_strip[index];
298 | if (i == 0) {
299 | pos = bar_align_line(bar, space_line, ALIGN_LEFT, ALIGN_CENTER);
300 | pos.x = bar_left_final_item_x;
301 | } else {
302 | pos.x += g_bar_manager.spacing_left;
303 | }
304 |
305 | bar_left_final_item_x = pos.x + g_bar_manager.spacing_left;
306 |
307 | if (sid == space_list[i]) {
308 | space_line.color = g_bar_manager.space_icon_color;
309 | }
310 | bar_draw_line(bar, space_line, pos.x, pos.y);
311 |
312 | }
313 |
314 | free(space_list);
315 | }
316 | }
317 |
318 | }
319 |
320 | if(g_bar_manager.left_shell_on) {
321 | struct bar_line lso_icon = g_bar_manager.left_shell_icon;
322 | lso_icon.color = g_bar_manager.left_shell_icon_color;
323 | CGPoint li_pos = bar_align_line(bar, lso_icon, 0, ALIGN_CENTER);
324 | li_pos.x = bar_left_final_item_x;
325 | bar_draw_line(bar, lso_icon, li_pos.x, li_pos.y);
326 |
327 | struct bar_line left_shell_line = bar_prepare_line(g_bar_manager.t_font, g_bar_manager.left_shell_output, g_bar_manager.foreground_color);
328 | CGPoint lso_pos = bar_align_line(bar, left_shell_line, ALIGN_LEFT, ALIGN_CENTER);
329 | lso_pos.x = li_pos.x + lso_icon.bounds.size.width + 5;
330 |
331 | bar_left_final_item_x = lso_pos.x + left_shell_line.bounds.size.width + g_bar_manager.spacing_left;
332 | bar_draw_line(bar, left_shell_line, lso_pos.x, lso_pos.y);
333 | }
334 |
335 | //
336 | // BAR RIGHT
337 | //
338 |
339 | // This is used to calculate overlap for the cetner bar.
340 | // It is updated to represent the X position of the first item, depending on what's displayed.
341 | int bar_right_first_item_x = bar->frame.size.width - g_bar_manager.padding_right;
342 | if (g_bar_manager.clock) {
343 | time_t rawtime;
344 | time(&rawtime);
345 | struct tm *timeinfo = localtime(&rawtime);
346 | if (timeinfo) {
347 | char time[255];
348 | strftime(time, sizeof(time), g_bar_manager._clock_format, timeinfo);
349 | struct bar_line time_line = bar_prepare_line(g_bar_manager.t_font, time, g_bar_manager.foreground_color);
350 | CGPoint t_pos = bar_align_line(bar, time_line, ALIGN_RIGHT, ALIGN_CENTER);
351 | t_pos.x = bar_right_first_item_x - time_line.bounds.size.width;
352 | bar_draw_line(bar, time_line, t_pos.x, t_pos.y);
353 |
354 | struct bar_line time_icon = g_bar_manager.clock_icon;
355 | time_icon.color = g_bar_manager.clock_icon_color;
356 | CGPoint ti_pos = bar_align_line(bar, time_icon, 0, ALIGN_CENTER);
357 | ti_pos.x = t_pos.x - time_icon.bounds.size.width - 5;
358 | bar_right_first_item_x = ti_pos.x - g_bar_manager.spacing_right;
359 |
360 | bar_draw_line(bar, time_icon, ti_pos.x, ti_pos.y);
361 | bar_destroy_line(time_line);
362 | }
363 | }
364 |
365 | if (g_bar_manager.power) {
366 | bool has_batt = false;
367 | bool charging = false;
368 | int percent = bar_find_battery_life(&has_batt, &charging);
369 | if (has_batt) {
370 | char batt[255];
371 | snprintf(batt, sizeof(batt), "%' '3d%%", percent);
372 | struct bar_line batt_line = bar_prepare_line(g_bar_manager.t_font, batt, g_bar_manager.foreground_color);
373 | CGPoint p_pos = bar_align_line(bar, batt_line, ALIGN_RIGHT, ALIGN_CENTER);
374 | p_pos.x = bar_right_first_item_x - batt_line.bounds.size.width;
375 | bar_draw_line(bar, batt_line, p_pos.x, p_pos.y);
376 |
377 | struct bar_line batt_icon = charging ? g_bar_manager.power_icon : g_bar_manager.battr_icon;
378 | batt_icon.color = charging ? g_bar_manager.power_icon_color : g_bar_manager.battery_icon_color;
379 | CGPoint pi_pos = bar_align_line(bar, batt_icon, 0, ALIGN_CENTER);
380 | pi_pos.x = p_pos.x - batt_icon.bounds.size.width;
381 | bar_right_first_item_x = pi_pos.x - g_bar_manager.spacing_right;
382 |
383 | bar_draw_line(bar, batt_icon, pi_pos.x, pi_pos.y);
384 | bar_destroy_line(batt_line);
385 | }
386 | }
387 |
388 | if (g_bar_manager.dnd) {
389 | bool dnd = getDoNotDisturb();
390 | if (dnd) {
391 | struct bar_line dnd_icon = g_bar_manager.dnd_icon;
392 | dnd_icon.color = g_bar_manager.dnd_icon_color;
393 | CGPoint di_pos = bar_align_line(bar, dnd_icon, 0, ALIGN_CENTER);
394 | di_pos.x = bar_right_first_item_x - dnd_icon.bounds.size.width;
395 | bar_right_first_item_x = di_pos.x - g_bar_manager.spacing_right;
396 |
397 | bar_draw_line(bar, dnd_icon, di_pos.x, di_pos.y);
398 | }
399 | }
400 |
401 | if(g_bar_manager.right_shell_on) {
402 | struct bar_line right_shell_line = bar_prepare_line(g_bar_manager.t_font, g_bar_manager.right_shell_output, g_bar_manager.foreground_color);
403 | CGPoint rso_pos = bar_align_line(bar, right_shell_line, ALIGN_RIGHT, ALIGN_CENTER);
404 | rso_pos.x = bar_right_first_item_x - right_shell_line.bounds.size.width;
405 | bar_draw_line(bar, right_shell_line, rso_pos.x, rso_pos.y);
406 |
407 | struct bar_line rso_icon = g_bar_manager.right_shell_icon;
408 | rso_icon.color = g_bar_manager.right_shell_icon_color;
409 | CGPoint ri_pos = bar_align_line(bar, rso_icon, 0, ALIGN_CENTER);
410 | ri_pos.x = rso_pos.x - rso_icon.bounds.size.width - 5;
411 | bar_right_first_item_x = ri_pos.x - g_bar_manager.spacing_right;
412 |
413 | bar_draw_line(bar, rso_icon, ri_pos.x, ri_pos.y);
414 | }
415 |
416 |
417 | // BAR CENTER
418 | if (g_bar_manager.title) {
419 | char *title = focused_window_title();
420 | if (title) {
421 | int overlap_right = 0;
422 |
423 | struct bar_line title_line = bar_prepare_line(g_bar_manager.t_font, title, g_bar_manager.foreground_color);
424 | CGPoint pos = bar_align_line(bar, title_line, ALIGN_CENTER, ALIGN_CENTER);
425 |
426 | if (bar_left_final_item_x >= pos.x) {
427 | pos.x = bar_left_final_item_x + 100;
428 | }
429 |
430 | if (bar_right_first_item_x <= (pos.x + title_line.bounds.size.width)) {
431 | overlap_right = (pos.x + title_line.bounds.size.width) - bar_right_first_item_x;
432 | }
433 |
434 | if (overlap_right > 0) {
435 | int truncated_width = (int)title_line.bounds.size.width - (overlap_right + 100);
436 | if (truncated_width > 0) {
437 | CTLineRef truncated_line = CTLineCreateTruncatedLine(title_line.line, truncated_width, kCTLineTruncationEnd, NULL);
438 | CFRelease(title_line.line);
439 | title_line.line = truncated_line;
440 | } else {
441 | goto free_title;
442 | }
443 | }
444 |
445 | bar_draw_line(bar, title_line, pos.x, pos.y);
446 | free_title:
447 | bar_destroy_line(title_line);
448 | free(title);
449 | }
450 | }
451 |
452 |
453 | if (g_bar_manager.center_shell_on) {
454 | int overlap_right = 0;
455 |
456 | struct bar_line center_shell_line = bar_prepare_line(g_bar_manager.t_font, g_bar_manager.center_shell_output, g_bar_manager.foreground_color);
457 | CGPoint pos = bar_align_line(bar, center_shell_line, ALIGN_CENTER, ALIGN_CENTER);
458 |
459 | if (bar_left_final_item_x >= pos.x) {
460 | pos.x = bar_left_final_item_x + 100;
461 | }
462 |
463 | if (bar_right_first_item_x <= (pos.x + center_shell_line.bounds.size.width)) {
464 | overlap_right = (pos.x + center_shell_line.bounds.size.width) - bar_right_first_item_x;
465 | }
466 |
467 | if (overlap_right > 0) {
468 | int truncated_width = (int)center_shell_line.bounds.size.width - (overlap_right + 100);
469 | if (truncated_width > 0) {
470 | CTLineRef truncated_line = CTLineCreateTruncatedLine(center_shell_line.line, truncated_width, kCTLineTruncationEnd, NULL);
471 | CFRelease(center_shell_line.line);
472 | center_shell_line.line = truncated_line;
473 | } else {
474 | goto destroy_center;
475 | }
476 | }
477 |
478 | bar_draw_line(bar, center_shell_line, pos.x, pos.y);
479 | destroy_center:
480 | bar_destroy_line(center_shell_line);
481 | }
482 |
483 |
484 | CGContextFlush(bar->context);
485 | SLSOrderWindow(g_connection, bar->id, 1, bar->id);
486 | SLSReenableUpdate(g_connection);
487 | }
488 |
489 | static CGPoint bar_create_frame(struct bar *bar, CFTypeRef *frame_region)
490 | {
491 | CGRect bounds = display_bounds(bar->did);
492 | CGPoint origin = bounds.origin;
493 |
494 | if (!display_manager_menu_bar_hidden()) {
495 | CGRect menu = display_manager_menu_bar_rect(bar->did);
496 | origin.y += menu.size.height;
497 | }
498 |
499 | char *btm = "bottom";
500 | CGFloat display_bottom = CGRectGetMaxY(bounds);
501 | if (strcmp(g_bar_manager.position, btm) == 0) {
502 | origin.y = display_bottom - g_bar_manager.height;
503 | }
504 |
505 | bar->frame = (CGRect) {{0, 0},{bounds.size.width, g_bar_manager.height}};
506 | CGSNewRegionWithRect(&bar->frame, frame_region);
507 |
508 | return origin;
509 | }
510 |
511 | void bar_resize(struct bar *bar)
512 | {
513 | CFTypeRef frame_region;
514 | CGPoint origin = bar_create_frame(bar, &frame_region);
515 |
516 | SLSDisableUpdate(g_connection);
517 | SLSOrderWindow(g_connection, bar->id, -1, 0);
518 | SLSSetWindowShape(g_connection, bar->id, origin.x, origin.y, frame_region);
519 | bar_refresh(bar);
520 | SLSOrderWindow(g_connection, bar->id, 1, 0);
521 | SLSReenableUpdate(g_connection);
522 | CFRelease(frame_region);
523 | }
524 |
525 | struct bar *bar_create(uint32_t did)
526 | {
527 | struct bar *bar = malloc(sizeof(struct bar));
528 | memset(bar, 0, sizeof(struct bar));
529 | bar->did = did;
530 |
531 | uint32_t set_tags[2] = {
532 | kCGSStickyTagBit |
533 | kCGSModalWindowTagBit |
534 | kCGSDisableShadowTagBit |
535 | kCGSHighQualityResamplingTagBit |
536 | kCGSIgnoreForExposeTagBit
537 | };
538 | // workaround: `kCGSDisableShadowTagBit` makes spacebar only show on one space on macOS 12, so we remove it
539 | if (__builtin_available(macOS 12.0, *)) {
540 | set_tags[0] =
541 | kCGSStickyTagBit |
542 | kCGSModalWindowTagBit |
543 | kCGSHighQualityResamplingTagBit |
544 | kCGSIgnoreForExposeTagBit;
545 | }
546 |
547 | uint32_t clear_tags[2] = { 0, 0 };
548 | *((int8_t *)(clear_tags) + 0x5) = 0x20;
549 |
550 | CFTypeRef frame_region;
551 | CGPoint origin = bar_create_frame(bar, &frame_region);
552 |
553 | SLSNewWindow(g_connection, 2, origin.x, origin.y, frame_region, &bar->id);
554 | CFRelease(frame_region);
555 |
556 | SLSSetWindowResolution(g_connection, bar->id, 2.0f);
557 | SLSSetWindowTags(g_connection, bar->id, set_tags, 64);
558 | SLSClearWindowTags(g_connection, bar->id, clear_tags, 64);
559 | SLSSetWindowOpacity(g_connection, bar->id, 0);
560 | SLSSetMouseEventEnableFlags(g_connection, bar->id, false);
561 | SLSSetWindowLevel(g_connection, bar->id, CGWindowLevelForKey(4));
562 | bar->context = SLWindowContextCreate(g_connection, bar->id, 0);
563 |
564 | // workaround: disable shadow on macOS 12
565 | if (__builtin_available(macOS 12.0, *)) {
566 | CFIndex shadow_density = 0;
567 | CFNumberRef shadow_density_cf = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &shadow_density);
568 | const void *keys[1] = { CFSTR("com.apple.WindowShadowDensity") };
569 | const void *values[1] = { shadow_density_cf };
570 | CFDictionaryRef shadow_props_cf = CFDictionaryCreate(NULL, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
571 | CGSWindowSetShadowProperties(bar->id, shadow_props_cf);
572 | CFRelease(shadow_density_cf);
573 | CFRelease(shadow_props_cf);
574 | }
575 |
576 | int refresh_frequency = 5;
577 | int shell_refresh_frequency = 5;
578 | bar->power_source = IOPSNotificationCreateRunLoopSource(power_handler, NULL);
579 | bar->refresh_timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + refresh_frequency, refresh_frequency, 0, 0, timer_handler, NULL);
580 | bar->shell_refresh_timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + shell_refresh_frequency, shell_refresh_frequency, 0, 0, shell_timer_handler, NULL);
581 |
582 | CFRunLoopAddSource(CFRunLoopGetMain(), bar->power_source, kCFRunLoopCommonModes);
583 | CFRunLoopAddTimer(CFRunLoopGetMain(), bar->refresh_timer, kCFRunLoopCommonModes);
584 | CFRunLoopAddTimer(CFRunLoopGetMain(), bar->shell_refresh_timer, kCFRunLoopCommonModes);
585 |
586 | bar_refresh(bar);
587 |
588 | return bar;
589 | }
590 |
591 | void bar_destroy(struct bar *bar)
592 | {
593 | CFRunLoopRemoveSource(CFRunLoopGetMain(), bar->power_source, kCFRunLoopCommonModes);
594 | CFRunLoopSourceInvalidate(bar->power_source);
595 |
596 | CFRunLoopRemoveTimer(CFRunLoopGetMain(), bar->refresh_timer, kCFRunLoopCommonModes);
597 | CFRunLoopTimerInvalidate(bar->refresh_timer);
598 |
599 | CFRunLoopRemoveTimer(CFRunLoopGetMain(), bar->shell_refresh_timer, kCFRunLoopCommonModes);
600 | CFRunLoopTimerInvalidate(bar->shell_refresh_timer);
601 |
602 | CGContextRelease(bar->context);
603 | SLSReleaseWindow(g_connection, bar->id);
604 | free(bar);
605 | }
606 |
--------------------------------------------------------------------------------
/src/bar.h:
--------------------------------------------------------------------------------
1 | #ifndef BAR_H
2 | #define BAR_H
3 |
4 | extern CGError SLSDisableUpdate(int cid);
5 | extern CGError SLSReenableUpdate(int cid);
6 | extern CGError SLSNewWindow(int cid, int type, float x, float y, CFTypeRef region, uint32_t *wid);
7 | extern CGError SLSReleaseWindow(int cid, uint32_t wid);
8 | extern CGError SLSSetWindowTags(int cid, uint32_t wid, uint32_t tags[2], int tag_size);
9 | extern CGError SLSClearWindowTags(int cid, uint32_t wid, uint32_t tags[2], int tag_size);
10 | extern CGError SLSSetWindowShape(int cid, uint32_t wid, float x_offset, float y_offset, CFTypeRef shape);
11 | extern CGError SLSSetWindowResolution(int cid, uint32_t wid, double res);
12 | extern CGError SLSSetWindowOpacity(int cid, uint32_t wid, bool isOpaque);
13 | extern CGError SLSSetMouseEventEnableFlags(int cid, uint32_t wid, bool shouldEnable);
14 | extern CGError SLSOrderWindow(int cid, uint32_t wid, int mode, uint32_t relativeToWID);
15 | extern CGError SLSSetWindowLevel(int cid, uint32_t wid, int level);
16 | extern CGContextRef SLWindowContextCreate(int cid, uint32_t wid, CFDictionaryRef options);
17 | extern CGError CGSNewRegionWithRect(CGRect *rect, CFTypeRef *outRegion);
18 | extern CGError CGSWindowSetShadowProperties(int wid, CFDictionaryRef properties);
19 |
20 | #define kCGSModalWindowTagBit (1 << 31)
21 | #define kCGSDisableShadowTagBit (1 << 3)
22 | #define kCGSHighQualityResamplingTagBit (1 << 4)
23 | #define kCGSIgnoreForExposeTagBit (1 << 7)
24 | #define kCGSStickyTagBit (1 << 11)
25 |
26 | #define POWER_CALLBACK(name) void name(void *context)
27 | typedef POWER_CALLBACK(power_callback);
28 |
29 | #define TIMER_CALLBACK(name) void name(CFRunLoopTimerRef timer, void *context)
30 | typedef TIMER_CALLBACK(timer_callback);
31 |
32 | #define SHELL_TIMER_CALLBACK(name) void name(CFRunLoopTimerRef timer, void *context)
33 | typedef SHELL_TIMER_CALLBACK(shell_timer_callback);
34 |
35 | #define ALIGN_NONE 0
36 | #define ALIGN_LEFT 1
37 | #define ALIGN_RIGHT 2
38 | #define ALIGN_TOP 3
39 | #define ALIGN_BOTTOM 4
40 | #define ALIGN_CENTER 5
41 |
42 | struct bar_line
43 | {
44 | CTLineRef line;
45 | CGFloat ascent;
46 | CGFloat descent;
47 | CGRect bounds;
48 | struct rgba_color color;
49 | };
50 |
51 | struct bar
52 | {
53 | uint32_t id;
54 | uint32_t did;
55 | CGContextRef context;
56 | CFRunLoopSourceRef power_source;
57 | CFRunLoopTimerRef refresh_timer;
58 | CFRunLoopTimerRef shell_refresh_timer;
59 | CGRect frame;
60 | };
61 |
62 | void bar_refresh(struct bar *bar);
63 | void bar_resize(struct bar *bar);
64 | struct bar *bar_create(uint32_t did);
65 | void bar_destroy(struct bar *bar);
66 |
67 | #endif
68 |
--------------------------------------------------------------------------------
/src/bar_manager.c:
--------------------------------------------------------------------------------
1 | #include "bar_manager.h"
2 |
3 | void bar_manager_set_foreground_color(struct bar_manager *bar_manager, uint32_t color)
4 | {
5 | bar_manager->foreground_color = rgba_color_from_hex(color);
6 | if (bar_manager->_space_icon_strip) bar_manager_set_space_strip(bar_manager, bar_manager->_space_icon_strip);
7 | if (bar_manager->_power_icon_strip) bar_manager_set_power_strip(bar_manager, bar_manager->_power_icon_strip);
8 | if (bar_manager->_clock_icon) bar_manager_set_clock_icon(bar_manager, bar_manager->_clock_icon);
9 | if (bar_manager->_dnd_icon) bar_manager_set_dnd_icon(bar_manager, bar_manager->_dnd_icon);
10 | if (bar_manager->_space_icon) bar_manager_set_space_icon(bar_manager, bar_manager->_space_icon);
11 | if (bar_manager->_left_shell_icon) bar_manager_set_left_shell_icon(bar_manager, bar_manager->_left_shell_icon);
12 | if (bar_manager->_right_shell_icon) bar_manager_set_right_shell_icon(bar_manager, bar_manager->_right_shell_icon);
13 | if (bar_manager->_display_separator_icon) bar_manager_set_display_separator_icon(bar_manager, bar_manager->_display_separator_icon);
14 | bar_manager_refresh(bar_manager);
15 | }
16 |
17 | void bar_manager_set_background_color(struct bar_manager *bar_manager, uint32_t color)
18 | {
19 | bar_manager->background_color = rgba_color_from_hex(color);
20 | bar_manager_refresh(bar_manager);
21 | }
22 |
23 | void bar_manager_set_space_icon_color(struct bar_manager *bar_manager, uint32_t color)
24 | {
25 | bar_manager->space_icon_color = rgba_color_from_hex(color);
26 | bar_manager_refresh(bar_manager);
27 | }
28 |
29 | void bar_manager_set_space_icon_color_secondary(struct bar_manager *bar_manager, uint32_t color)
30 | {
31 | bar_manager->space_icon_color_secondary = rgba_color_from_hex(color);
32 | bar_manager_refresh(bar_manager);
33 | }
34 |
35 | void bar_manager_set_space_icon_color_tertiary(struct bar_manager *bar_manager, uint32_t color)
36 | {
37 | bar_manager->space_icon_color_tertiary = rgba_color_from_hex(color);
38 | bar_manager_refresh(bar_manager);
39 | }
40 |
41 | void bar_manager_set_battery_icon_color(struct bar_manager *bar_manager, uint32_t color)
42 | {
43 | bar_manager->battery_icon_color = rgba_color_from_hex(color);
44 | bar_manager_refresh(bar_manager);
45 | }
46 |
47 | void bar_manager_set_power_icon_color(struct bar_manager *bar_manager, uint32_t color)
48 | {
49 | bar_manager->power_icon_color = rgba_color_from_hex(color);
50 | bar_manager_refresh(bar_manager);
51 | }
52 |
53 | void bar_manager_set_clock_icon_color(struct bar_manager *bar_manager, uint32_t color)
54 | {
55 | bar_manager->clock_icon_color = rgba_color_from_hex(color);
56 | bar_manager_refresh(bar_manager);
57 | }
58 |
59 | void bar_manager_set_dnd_icon_color(struct bar_manager *bar_manager, uint32_t color)
60 | {
61 | bar_manager->dnd_icon_color = rgba_color_from_hex(color);
62 | bar_manager_refresh(bar_manager);
63 | }
64 |
65 | void bar_manager_set_left_shell_icon_color(struct bar_manager *bar_manager, uint32_t color)
66 | {
67 | bar_manager->left_shell_icon_color = rgba_color_from_hex(color);
68 | bar_manager_refresh(bar_manager);
69 | }
70 |
71 | void bar_manager_set_right_shell_icon_color(struct bar_manager *bar_manager, uint32_t color)
72 | {
73 | bar_manager->right_shell_icon_color = rgba_color_from_hex(color);
74 | bar_manager_refresh(bar_manager);
75 | }
76 |
77 | void bar_manager_set_display_separator_icon_color(struct bar_manager *bar_manager, uint32_t color)
78 | {
79 | bar_manager->display_separator_icon_color = rgba_color_from_hex(color);
80 | bar_manager_refresh(bar_manager);
81 | }
82 |
83 | void bar_manager_set_text_font(struct bar_manager *bar_manager, char *font_string)
84 | {
85 | if (bar_manager->t_font) {
86 | CFRelease(bar_manager->t_font);
87 | }
88 |
89 | if (font_string != bar_manager->t_font_prop) {
90 | if (bar_manager->t_font_prop) {
91 | free(bar_manager->t_font_prop);
92 | }
93 |
94 | bar_manager->t_font_prop = font_string;
95 | }
96 |
97 | bar_manager->t_font = bar_create_font(bar_manager->t_font_prop);
98 | bar_manager_refresh(bar_manager);
99 | }
100 |
101 | void bar_manager_set_icon_font(struct bar_manager *bar_manager, char *font_string)
102 | {
103 | if (bar_manager->i_font) {
104 | CFRelease(bar_manager->i_font);
105 | }
106 |
107 | if (font_string != bar_manager->i_font_prop) {
108 | if (bar_manager->i_font_prop) {
109 | free(bar_manager->i_font_prop);
110 | }
111 |
112 | bar_manager->i_font_prop = font_string;
113 | }
114 |
115 | bar_manager->i_font = bar_create_font(bar_manager->i_font_prop);
116 | if (bar_manager->_space_icon_strip) bar_manager_set_space_strip(bar_manager, bar_manager->_space_icon_strip);
117 | if (bar_manager->_power_icon_strip) bar_manager_set_power_strip(bar_manager, bar_manager->_power_icon_strip);
118 | if (bar_manager->_clock_icon) bar_manager_set_clock_icon(bar_manager, bar_manager->_clock_icon);
119 | if (bar_manager->_space_icon) bar_manager_set_space_icon(bar_manager, bar_manager->_space_icon);
120 | if (bar_manager->_dnd_icon) bar_manager_set_dnd_icon(bar_manager, bar_manager->_dnd_icon);
121 | if (bar_manager->_left_shell_icon) bar_manager_set_left_shell_icon(bar_manager, bar_manager->_left_shell_icon);
122 | if (bar_manager->_right_shell_icon) bar_manager_set_right_shell_icon(bar_manager, bar_manager->_right_shell_icon);
123 | if (bar_manager->_display_separator_icon) bar_manager_set_display_separator_icon(bar_manager, bar_manager->_display_separator_icon);
124 | bar_manager_refresh(bar_manager);
125 | }
126 |
127 | void bar_manager_set_space_strip(struct bar_manager *bar_manager, char **icon_strip)
128 | {
129 | for (int i = 0; i < buf_len(bar_manager->space_icon_strip); ++i) {
130 | bar_destroy_line(bar_manager->space_icon_strip[i]);
131 | }
132 |
133 | buf_free(bar_manager->space_icon_strip);
134 | bar_manager->space_icon_strip = NULL;
135 |
136 | if (icon_strip != bar_manager->_space_icon_strip) {
137 | for (int i = 0; i < buf_len(bar_manager->_space_icon_strip); ++i) {
138 | free(bar_manager->_space_icon_strip[i]);
139 | }
140 |
141 | buf_free(bar_manager->_space_icon_strip);
142 | bar_manager->_space_icon_strip = icon_strip;
143 | }
144 |
145 | for (int i = 0; i < buf_len(bar_manager->_space_icon_strip); ++i) {
146 | struct bar_line space_line = bar_prepare_line(bar_manager->i_font, bar_manager->_space_icon_strip[i], bar_manager->foreground_color);
147 | buf_push(bar_manager->space_icon_strip, space_line);
148 | }
149 |
150 | bar_manager_refresh(bar_manager);
151 | }
152 |
153 | void bar_manager_set_power_strip(struct bar_manager *bar_manager, char **icon_strip)
154 | {
155 | if (bar_manager->battr_icon.line) {
156 | bar_destroy_line(bar_manager->battr_icon);
157 | }
158 |
159 | if (bar_manager->power_icon.line) {
160 | bar_destroy_line(bar_manager->power_icon);
161 | }
162 |
163 | if (icon_strip != bar_manager->_power_icon_strip) {
164 | for (int i = 0; i < buf_len(bar_manager->_power_icon_strip); ++i) {
165 | free(bar_manager->_power_icon_strip[i]);
166 | }
167 |
168 | buf_free(bar_manager->_power_icon_strip);
169 | bar_manager->_power_icon_strip = icon_strip;
170 | }
171 |
172 | if (buf_len(bar_manager->_power_icon_strip) == 2) {
173 | bar_manager->battr_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_power_icon_strip[0], bar_manager->battery_icon_color);
174 | bar_manager->power_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_power_icon_strip[1], bar_manager->power_icon_color);
175 | } else {
176 | bar_manager->battr_icon = bar_prepare_line(bar_manager->i_font, "", bar_manager->battery_icon_color);
177 | bar_manager->power_icon = bar_prepare_line(bar_manager->i_font, "", bar_manager->power_icon_color);
178 | }
179 |
180 | bar_manager_refresh(bar_manager);
181 | }
182 |
183 | void bar_manager_set_clock_icon(struct bar_manager *bar_manager, char *icon)
184 | {
185 | if (bar_manager->clock_icon.line) {
186 | bar_destroy_line(bar_manager->clock_icon);
187 | }
188 |
189 | if (icon != bar_manager->_clock_icon) {
190 | if (bar_manager->_clock_icon) {
191 | free(bar_manager->_clock_icon);
192 | }
193 |
194 | bar_manager->_clock_icon = icon;
195 | }
196 |
197 | bar_manager->clock_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_clock_icon, bar_manager->foreground_color);
198 |
199 | bar_manager_refresh(bar_manager);
200 | }
201 |
202 | void bar_manager_set_clock_format(struct bar_manager *bar_manager, char *format)
203 | {
204 | bar_manager->_clock_format = format;
205 | bar_manager_set_text_font(bar_manager, bar_manager->t_font_prop);
206 | }
207 |
208 |
209 | void bar_manager_set_space_icon(struct bar_manager *bar_manager, char *icon)
210 | {
211 | if (bar_manager->space_icon.line) {
212 | bar_destroy_line(bar_manager->space_icon);
213 | }
214 |
215 | if (icon != bar_manager->_space_icon) {
216 | if (bar_manager->_space_icon) {
217 | free(bar_manager->_space_icon);
218 | }
219 |
220 | bar_manager->_space_icon = icon;
221 | }
222 |
223 | bar_manager->space_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_space_icon, bar_manager->foreground_color);
224 |
225 | bar_manager_refresh(bar_manager);
226 | }
227 |
228 | void bar_manager_set_dnd_icon(struct bar_manager *bar_manager, char *icon)
229 | {
230 | if (bar_manager->dnd_icon.line) {
231 | bar_destroy_line(bar_manager->dnd_icon);
232 | }
233 |
234 | if (icon != bar_manager->_dnd_icon) {
235 | if (bar_manager->_dnd_icon) {
236 | free(bar_manager->_dnd_icon);
237 | }
238 |
239 | bar_manager->_dnd_icon = icon;
240 | }
241 |
242 | bar_manager->dnd_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_dnd_icon, bar_manager->dnd_icon_color);
243 |
244 | bar_manager_refresh(bar_manager);
245 | }
246 |
247 | void bar_manager_set_left_shell_icon(struct bar_manager *bar_manager, char *icon)
248 | {
249 | if (bar_manager->left_shell_icon.line) {
250 | bar_destroy_line(bar_manager->left_shell_icon);
251 | }
252 |
253 | if (icon != bar_manager->_left_shell_icon) {
254 | if (bar_manager->_left_shell_icon) {
255 | free(bar_manager->_left_shell_icon);
256 | }
257 |
258 | bar_manager->_left_shell_icon = icon;
259 | }
260 |
261 | bar_manager->left_shell_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_left_shell_icon, bar_manager->left_shell_icon_color);
262 |
263 | bar_manager_refresh(bar_manager);
264 | }
265 |
266 | void bar_manager_set_right_shell_icon(struct bar_manager *bar_manager, char *icon)
267 | {
268 | if (bar_manager->right_shell_icon.line) {
269 | bar_destroy_line(bar_manager->right_shell_icon);
270 | }
271 |
272 | if (icon != bar_manager->_right_shell_icon) {
273 | if (bar_manager->_right_shell_icon) {
274 | free(bar_manager->_right_shell_icon);
275 | }
276 |
277 | bar_manager->_right_shell_icon = icon;
278 | }
279 |
280 | bar_manager->right_shell_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_right_shell_icon, bar_manager->right_shell_icon_color);
281 |
282 | bar_manager_refresh(bar_manager);
283 | }
284 |
285 | void bar_manager_set_display_separator_icon(struct bar_manager *bar_manager, char *icon)
286 | {
287 | if (bar_manager->display_separator_icon.line) {
288 | bar_destroy_line(bar_manager->display_separator_icon);
289 | }
290 |
291 | if (icon != bar_manager->_display_separator_icon) {
292 | if (bar_manager->_display_separator_icon) {
293 | free(bar_manager->_display_separator_icon);
294 | }
295 |
296 | bar_manager->_display_separator_icon = icon;
297 | }
298 |
299 | bar_manager->display_separator_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_display_separator_icon, bar_manager->display_separator_icon_color);
300 |
301 | bar_manager_refresh(bar_manager);
302 | }
303 |
304 | void bar_manager_set_position(struct bar_manager *bar_manager, char *pos)
305 | {
306 | bar_manager->position = pos;
307 | bar_manager_resize(bar_manager);
308 | }
309 |
310 | void bar_manager_set_title(struct bar_manager *bar_manager, bool value)
311 | {
312 | bar_manager->title = value;
313 | bar_manager_refresh(bar_manager);
314 | }
315 |
316 | void bar_manager_set_dnd(struct bar_manager *bar_manager, bool value)
317 | {
318 | bar_manager->dnd = value;
319 | bar_manager_refresh(bar_manager);
320 | }
321 |
322 | void bar_manager_set_spaces(struct bar_manager *bar_manager, bool value)
323 | {
324 | bar_manager->spaces = value;
325 | bar_manager_refresh(bar_manager);
326 | }
327 |
328 | void bar_manager_set_spaces_for_all_displays(struct bar_manager *bar_manager, bool value)
329 | {
330 | bar_manager->spaces_for_all_displays = value;
331 | bar_manager_refresh(bar_manager);
332 | }
333 |
334 | void bar_manager_set_display_separator(struct bar_manager *bar_manager, bool value)
335 | {
336 | bar_manager->display_separator = value;
337 | bar_manager_refresh(bar_manager);
338 | }
339 |
340 | void bar_manager_set_clock(struct bar_manager *bar_manager, bool value)
341 | {
342 | bar_manager->clock = value;
343 | bar_manager_refresh(bar_manager);
344 | }
345 |
346 | void bar_manager_set_power(struct bar_manager *bar_manager, bool value)
347 | {
348 | bar_manager->power = value;
349 | bar_manager_refresh(bar_manager);
350 | }
351 |
352 | void bar_manager_set_height(struct bar_manager *bar_manager, uint32_t height)
353 | {
354 | bar_manager->height = height;
355 | bar_manager_resize(bar_manager);
356 | }
357 |
358 | void bar_manager_set_padding_left(struct bar_manager *bar_manager, uint32_t padding)
359 | {
360 | bar_manager->padding_left = padding;
361 | bar_manager_refresh(bar_manager);
362 | }
363 |
364 | void bar_manager_set_padding_right(struct bar_manager *bar_manager, uint32_t padding)
365 | {
366 | bar_manager->padding_right = padding;
367 | bar_manager_refresh(bar_manager);
368 | }
369 |
370 | void bar_manager_set_spacing_left(struct bar_manager *bar_manager, uint32_t spacing)
371 | {
372 | bar_manager->spacing_left = spacing;
373 | bar_manager_refresh(bar_manager);
374 | }
375 |
376 | void bar_manager_set_spacing_right(struct bar_manager *bar_manager, uint32_t spacing)
377 | {
378 | bar_manager->spacing_right = spacing;
379 | bar_manager_refresh(bar_manager);
380 | }
381 |
382 | void bar_manager_set_left_shell(struct bar_manager *bar_manager, bool value)
383 | {
384 | bar_manager->left_shell_on = value;
385 | set_shell_outputs(bar_manager);
386 | bar_manager_refresh(bar_manager);
387 | }
388 |
389 | void bar_manager_set_right_shell(struct bar_manager *bar_manager, bool value)
390 | {
391 | bar_manager->right_shell_on = value;
392 | set_shell_outputs(bar_manager);
393 | bar_manager_refresh(bar_manager);
394 | }
395 |
396 | void bar_manager_set_center_shell(struct bar_manager *bar_manager, bool value)
397 | {
398 | bar_manager->center_shell_on = value;
399 | set_shell_outputs(bar_manager);
400 | bar_manager_refresh(bar_manager);
401 | }
402 |
403 | void bar_manager_set_left_shell_output(struct bar_manager *bar_manager, char *output)
404 | {
405 | bar_manager->left_shell_output = output;
406 | }
407 |
408 | void bar_manager_set_right_shell_output(struct bar_manager *bar_manager, char *output)
409 | {
410 | bar_manager->right_shell_output = output;
411 | }
412 |
413 | void bar_manager_set_center_shell_output(struct bar_manager *bar_manager, char *output)
414 | {
415 | bar_manager->center_shell_output = output;
416 | }
417 |
418 | void bar_manager_set_left_shell_command(struct bar_manager *bar_manager, char *command)
419 | {
420 | bar_manager->left_shell_command = command;
421 | set_shell_outputs(bar_manager);
422 | bar_manager_refresh(bar_manager);
423 | }
424 |
425 | void bar_manager_set_right_shell_command(struct bar_manager *bar_manager, char *command)
426 | {
427 | bar_manager->right_shell_command = command;
428 | set_shell_outputs(bar_manager);
429 | bar_manager_refresh(bar_manager);
430 | }
431 |
432 | void bar_manager_set_center_shell_command(struct bar_manager *bar_manager, char *command)
433 | {
434 | bar_manager->center_shell_command = command;
435 | set_shell_outputs(bar_manager);
436 | bar_manager_refresh(bar_manager);
437 | }
438 |
439 | void bar_manager_display_changed(struct bar_manager *bar_manager)
440 | {
441 | for (int i = 0; i < bar_manager->bar_count; ++i)
442 | bar_destroy(bar_manager->bars[i]);
443 |
444 | bar_manager_begin(bar_manager);
445 | }
446 |
447 | void bar_manager_set_display(struct bar_manager *bar_manager, char *display)
448 | {
449 | bar_manager->display = display;
450 |
451 | for (int i = 0; i < bar_manager->bar_count; ++i)
452 | bar_destroy(bar_manager->bars[i]);
453 |
454 | bar_manager_begin(bar_manager);
455 | }
456 |
457 | void bar_manager_refresh(struct bar_manager *bar_manager)
458 | {
459 | for (int i = 0; i < bar_manager->bar_count; ++i)
460 | bar_refresh(bar_manager->bars[i]);
461 | }
462 |
463 | void bar_manager_resize(struct bar_manager *bar_manager)
464 | {
465 | for (int i = 0; i < bar_manager->bar_count; ++i)
466 | bar_resize(bar_manager->bars[i]);
467 | }
468 |
469 |
470 | // TODO (cmacrae): Implement timeout
471 | static char* run_shell(char *command)
472 | {
473 | int cursor = 0;
474 | int bytes_read = 0;
475 | char *result = NULL;
476 | char buffer[BUFSIZ];
477 |
478 | FILE *handle = popen(command, "r");
479 | if (!handle) goto err;
480 |
481 | while ((bytes_read = read(fileno(handle), buffer, sizeof(buffer)-1)) > 0) {
482 | char *temp = realloc(result, cursor+bytes_read+1);
483 | if (!temp) goto err;
484 |
485 | result = temp;
486 | memcpy(result+cursor, buffer, bytes_read);
487 | cursor += bytes_read;
488 | }
489 |
490 | if (result && bytes_read != -1) {
491 | result[cursor] = '\0';
492 |
493 | pclose(handle);
494 | return result;
495 | } else {
496 | err:
497 | pclose(handle);
498 | return string_copy("error running command");
499 | if (result) free(result);
500 | }
501 |
502 | pclose(handle);
503 | return result;
504 | }
505 |
506 | void set_shell_outputs(struct bar_manager *bar_manager)
507 | {
508 | char* left_shell_output;
509 | char* right_shell_output;
510 | char* center_shell_output;
511 | if ((strlen(bar_manager->left_shell_command) > 0) && bar_manager->left_shell_on) {
512 | left_shell_output = run_shell(string_copy(bar_manager->left_shell_command));
513 | if (strlen(left_shell_output) > 0) {
514 | bar_manager_set_left_shell_output(bar_manager, left_shell_output);
515 | }
516 | }
517 | if ((strlen(bar_manager->right_shell_command) > 0) && bar_manager->right_shell_on) {
518 | right_shell_output = run_shell(string_copy(bar_manager->right_shell_command));
519 | if (strlen(right_shell_output) > 0) {
520 | bar_manager_set_right_shell_output(bar_manager, right_shell_output);
521 | }
522 | }
523 | if ((strlen(bar_manager->center_shell_command) > 0) && bar_manager->center_shell_on) {
524 | center_shell_output = run_shell(string_copy(bar_manager->center_shell_command));
525 | if (strlen(center_shell_output) > 0) {
526 | bar_manager_set_center_shell_output(bar_manager, center_shell_output);
527 | }
528 | }
529 | }
530 |
531 | void bar_manager_init(struct bar_manager *bar_manager)
532 | {
533 | bar_manager->bars = NULL;
534 | bar_manager->bar_count = 0;
535 | bar_manager->display = "all";
536 | bar_manager->position = "top";
537 | bar_manager->height = 26;
538 | bar_manager->title = true;
539 | bar_manager->spaces = true;
540 | bar_manager->clock = true;
541 | bar_manager->power = true;
542 | bar_manager->dnd = true;
543 | bar_manager->padding_left = 20;
544 | bar_manager->padding_right = 20;
545 | bar_manager->spacing_left = 25;
546 | bar_manager->spacing_right = 15;
547 | bar_manager_set_text_font(bar_manager, string_copy("Helvetica Neue:Regular:12.0"));
548 | bar_manager_set_icon_font(bar_manager, string_copy("Font Awesome 5 Free:Solid:12.0"));
549 | bar_manager->background_color = rgba_color_from_hex(0xff202020);
550 | bar_manager->foreground_color = rgba_color_from_hex(0xffa8a8a8);
551 | bar_manager->space_icon_color = rgba_color_from_hex(0xffd75f5f);
552 | bar_manager->space_icon_color_secondary = rgba_color_from_hex(0xffd75f5f);
553 | bar_manager->space_icon_color_tertiary = rgba_color_from_hex(0xffd75f5f);
554 | bar_manager->battery_icon_color = rgba_color_from_hex(0xffd75f5f);
555 | bar_manager->power_icon_color = rgba_color_from_hex(0xffcd950c);
556 | bar_manager->clock_icon_color = rgba_color_from_hex(0xffa8a8a8);
557 | bar_manager_set_clock_icon(bar_manager, string_copy(""));
558 | bar_manager->_clock_format = "%R";
559 | bar_manager_set_space_icon(bar_manager, string_copy("•"));
560 | bar_manager_set_power_strip(bar_manager, NULL);
561 | bar_manager_set_dnd_icon(bar_manager, string_copy(""));
562 | bar_manager_set_dnd_icon_color(bar_manager, 0xffa8a8a8);
563 | bar_manager->left_shell_on = false;
564 | bar_manager->right_shell_on = false;
565 | bar_manager->center_shell_on = false;
566 | bar_manager_set_left_shell_icon(bar_manager, string_copy(""));
567 | bar_manager->left_shell_icon_color = rgba_color_from_hex(0xffa8a8a8);
568 | bar_manager_set_right_shell_icon(bar_manager, string_copy(""));
569 | bar_manager->right_shell_icon_color = rgba_color_from_hex(0xffa8a8a8);
570 | bar_manager->left_shell_output = "";
571 | bar_manager->right_shell_output = "";
572 | bar_manager->center_shell_output = "";
573 | bar_manager->center_shell_output = "";
574 | bar_manager->left_shell_command = "echo 'left shell'";
575 | bar_manager->right_shell_command = "echo 'right shell'";
576 | bar_manager->center_shell_command = "echo 'center shell'";
577 | bar_manager_set_display_separator_icon(bar_manager, string_copy("|"));
578 | bar_manager->display_separator_icon_color = rgba_color_from_hex(0xffa8a8a8);
579 | }
580 |
581 | void bar_manager_begin(struct bar_manager *bar_manager)
582 | {
583 | char * main = "main";
584 | char * all = "all";
585 |
586 | if (strcmp(bar_manager->display,main) == 0) {
587 | uint32_t did = display_manager_main_display_id();
588 | bar_manager->bars = (struct bar **) realloc(bar_manager->bars, sizeof(struct bar *) * 1);
589 | bar_manager->bar_count = 1;
590 | bar_manager->bars[0] = bar_create(did);
591 | } else if (strcmp(bar_manager->display,all) == 0) {
592 | bar_manager->bar_count = display_manager_active_display_count();
593 | bar_manager->bars = (struct bar **) realloc(bar_manager->bars, sizeof(struct bar *) * bar_manager->bar_count);
594 |
595 | for (uint32_t index=1; index <= bar_manager->bar_count; index++) {
596 | uint32_t did = display_manager_arrangement_display_id(index);
597 | bar_manager->bars[index - 1] = bar_create(did);
598 | }
599 | }
600 | }
601 |
--------------------------------------------------------------------------------
/src/bar_manager.h:
--------------------------------------------------------------------------------
1 | #ifndef BAR_MANAGER_H
2 | #define BAR_MANAGER_H
3 |
4 | struct bar_manager
5 | {
6 | struct bar **bars;
7 | int bar_count;
8 | char *t_font_prop;
9 | char *i_font_prop;
10 | CTFontRef t_font;
11 | CTFontRef i_font;
12 | char **_space_icon_strip;
13 | char **_power_icon_strip;
14 | char *_clock_icon;
15 | char *_clock_format;
16 | char *_space_icon;
17 | char *_dnd_icon;
18 | char *_left_shell_icon;
19 | char *_right_shell_icon;
20 | char *position;
21 | char *display;
22 | char *_display_separator_icon;
23 | uint32_t height;
24 | uint32_t padding_left;
25 | uint32_t padding_right;
26 | uint32_t spacing_left;
27 | uint32_t spacing_right;
28 | bool title;
29 | bool spaces;
30 | bool spaces_for_all_displays;
31 | bool display_separator;
32 | bool clock;
33 | bool power;
34 | bool dnd;
35 | bool left_shell_on;
36 | bool right_shell_on;
37 | bool center_shell_on;
38 | char *left_shell_output;
39 | char *right_shell_output;
40 | char *center_shell_output;
41 | char *left_shell_command;
42 | char *right_shell_command;
43 | char *center_shell_command;
44 | struct rgba_color foreground_color;
45 | struct rgba_color background_color;
46 | struct rgba_color space_icon_color;
47 | struct rgba_color space_icon_color_secondary;
48 | struct rgba_color space_icon_color_tertiary;
49 | struct rgba_color battery_icon_color;
50 | struct rgba_color power_icon_color;
51 | struct rgba_color clock_icon_color;
52 | struct rgba_color dnd_icon_color;
53 | struct rgba_color left_shell_icon_color;
54 | struct rgba_color right_shell_icon_color;
55 | struct rgba_color display_separator_icon_color;
56 | struct rgba_color background_color_dim;
57 | struct bar_line *space_icon_strip;
58 | struct bar_line space_icon;
59 | struct bar_line clock_icon;
60 | struct bar_line battr_icon;
61 | struct bar_line power_icon;
62 | struct bar_line dnd_icon;
63 | struct bar_line left_shell;
64 | struct bar_line left_shell_icon;
65 | struct bar_line right_shell;
66 | struct bar_line right_shell_icon;
67 | struct bar_line center_shell;
68 | struct bar_line display_separator_icon;
69 | };
70 |
71 | void bar_manager_set_foreground_color(struct bar_manager *bar_manager, uint32_t color);
72 | void bar_manager_set_background_color(struct bar_manager *bar_manager, uint32_t color);
73 | void bar_manager_set_space_icon_color(struct bar_manager *bar_manager, uint32_t color);
74 | void bar_manager_set_space_icon_color_secondary(struct bar_manager *bar_manager, uint32_t color);
75 | void bar_manager_set_space_icon_color_tertiary(struct bar_manager *bar_manager, uint32_t color);
76 | void bar_manager_set_battery_icon_color(struct bar_manager *bar_manager, uint32_t color);
77 | void bar_manager_set_power_icon_color(struct bar_manager *bar_manager, uint32_t color);
78 | void bar_manager_set_clock_icon_color(struct bar_manager *bar_manager, uint32_t color);
79 | void bar_manager_set_dnd_icon_color(struct bar_manager *bar_manager, uint32_t color);
80 | void bar_manager_set_display_separator_icon_color(struct bar_manager *bar_manager, uint32_t color);
81 | void bar_manager_set_left_shell_icon_color(struct bar_manager *bar_manager, uint32_t color);
82 | void bar_manager_set_right_shell_icon_color(struct bar_manager *bar_manager, uint32_t color);
83 | void bar_manager_set_text_font(struct bar_manager *bar_manager, char *font_string);
84 | void bar_manager_set_icon_font(struct bar_manager *bar_manager, char *font_string);
85 | void bar_manager_set_space_strip(struct bar_manager *bar_manager, char **icon_strip);
86 | void bar_manager_set_power_strip(struct bar_manager *bar_manager, char **icon_strip);
87 | void bar_manager_set_clock_icon(struct bar_manager *bar_manager, char *icon);
88 | void bar_manager_set_clock_format(struct bar_manager *bar_manager, char *format);
89 | void bar_manager_set_space_icon(struct bar_manager *bar_manager, char *icon);
90 | void bar_manager_set_dnd(struct bar_manager *bar_manager, bool value);
91 | void bar_manager_set_dnd_icon(struct bar_manager *bar_manager, char *icon);
92 | void bar_manager_set_left_shell_icon(struct bar_manager *bar_manager, char *icon);
93 | void bar_manager_set_display_separator(struct bar_manager *bar_manager, bool value);
94 | void bar_manager_set_display_separator_icon(struct bar_manager *bar_manager, char *icon);
95 | void bar_manager_set_right_shell_icon(struct bar_manager *bar_manager, char *icon);
96 | void bar_manager_set_position(struct bar_manager *bar_manager, char *pos);
97 | void bar_manager_set_title(struct bar_manager *bar_manager, bool value);
98 | void bar_manager_set_spaces(struct bar_manager *bar_manager, bool value);
99 | void bar_manager_set_spaces_for_all_displays(struct bar_manager *bar_manager, bool value);
100 | void bar_manager_set_clock(struct bar_manager *bar_manager, bool value);
101 | void bar_manager_set_power(struct bar_manager *bar_manager, bool value);
102 | void bar_manager_set_height(struct bar_manager *bar_manager, uint32_t height);
103 | void bar_manager_set_padding_left(struct bar_manager *bar_manager, uint32_t padding);
104 | void bar_manager_set_padding_right(struct bar_manager *bar_manager, uint32_t padding);
105 | void bar_manager_set_spacing_left(struct bar_manager *bar_manager, uint32_t spacing);
106 | void bar_manager_set_spacing_right(struct bar_manager *bar_manager, uint32_t spacing);
107 | void bar_manager_set_left_shell(struct bar_manager *bar_manager, bool value);
108 | void bar_manager_set_right_shell(struct bar_manager *bar_manager, bool value);
109 | void bar_manager_set_center_shell(struct bar_manager *bar_manager, bool value);
110 | void bar_manager_set_left_shell_output(struct bar_manager *bar_manager, char *output);
111 | void bar_manager_set_right_shell_output(struct bar_manager *bar_manager, char *output);
112 | void bar_manager_set_center_shell_output(struct bar_manager *bar_manager, char *output);
113 | void bar_manager_set_left_shell_command(struct bar_manager *bar_manager, char *command);
114 | void bar_manager_set_right_shell_command(struct bar_manager *bar_manager, char *command);
115 | void bar_manager_set_center_shell_command(struct bar_manager *bar_manager, char *command);
116 | void bar_manager_set_display(struct bar_manager *bar_manager, char *display);
117 |
118 | void bar_manager_display_changed(struct bar_manager *bar_manager);
119 | void bar_manager_refresh(struct bar_manager *bar_manager);
120 | void bar_manager_resize(struct bar_manager *bar_manager);
121 | void bar_manager_begin(struct bar_manager *bar_manager);
122 | void bar_manager_init(struct bar_manager *bar_manager);
123 |
124 | void set_shell_outputs(struct bar_manager *bar_manager);
125 |
126 | #endif
127 |
--------------------------------------------------------------------------------
/src/display.c:
--------------------------------------------------------------------------------
1 | #include "display.h"
2 |
3 | extern struct event_loop g_event_loop;
4 | extern struct bar g_bar;
5 | extern int g_connection;
6 |
7 | static DISPLAY_EVENT_HANDLER(display_handler)
8 | {
9 | if (flags & kCGDisplayAddFlag) {
10 | struct event *event = event_create(&g_event_loop, DISPLAY_ADDED, (void *)(intptr_t) did);
11 | event_loop_post(&g_event_loop, event);
12 | } else if (flags & kCGDisplayRemoveFlag) {
13 | struct event *event = event_create(&g_event_loop, DISPLAY_REMOVED, (void *)(intptr_t) did);
14 | event_loop_post(&g_event_loop, event);
15 | } else if (flags & kCGDisplayMovedFlag) {
16 | struct event *event = event_create(&g_event_loop, DISPLAY_MOVED, (void *)(intptr_t) did);
17 | event_loop_post(&g_event_loop, event);
18 | } else if (flags & kCGDisplayDesktopShapeChangedFlag) {
19 | struct event *event = event_create(&g_event_loop, DISPLAY_RESIZED, (void *)(intptr_t) did);
20 | event_loop_post(&g_event_loop, event);
21 | }
22 | }
23 |
24 | CFStringRef display_uuid(uint32_t did)
25 | {
26 | CFUUIDRef uuid_ref = CGDisplayCreateUUIDFromDisplayID(did);
27 | if (!uuid_ref) return NULL;
28 |
29 | CFStringRef uuid_str = CFUUIDCreateString(NULL, uuid_ref);
30 | CFRelease(uuid_ref);
31 |
32 | return uuid_str;
33 | }
34 |
35 | CGRect display_bounds(uint32_t did)
36 | {
37 | return CGDisplayBounds(did);
38 | }
39 |
40 | uint64_t display_space_id(uint32_t did)
41 | {
42 | CFStringRef uuid = display_uuid(did);
43 | if (!uuid) return 0;
44 |
45 | uint64_t sid = SLSManagedDisplayGetCurrentSpace(g_connection, uuid);
46 | CFRelease(uuid);
47 | return sid;
48 | }
49 |
50 | uint64_t *display_space_list(uint32_t did, int *count)
51 | {
52 | CFStringRef uuid = display_uuid(did);
53 | if (!uuid) return NULL;
54 |
55 | CFArrayRef display_spaces_ref = SLSCopyManagedDisplaySpaces(g_connection);
56 | if (!display_spaces_ref) return NULL;
57 |
58 | uint64_t *space_list = NULL;
59 | int display_spaces_count = CFArrayGetCount(display_spaces_ref);
60 |
61 | for (int i = 0; i < display_spaces_count; ++i) {
62 | CFDictionaryRef display_ref = CFArrayGetValueAtIndex(display_spaces_ref, i);
63 | CFStringRef identifier = CFDictionaryGetValue(display_ref, CFSTR("Display Identifier"));
64 | if (!CFEqual(uuid, identifier)) continue;
65 |
66 | CFArrayRef spaces_ref = CFDictionaryGetValue(display_ref, CFSTR("Spaces"));
67 | int spaces_count = CFArrayGetCount(spaces_ref);
68 |
69 | space_list = malloc(sizeof(uint64_t) * spaces_count);
70 | *count = spaces_count;
71 |
72 | for (int j = 0; j < spaces_count; ++j) {
73 | CFDictionaryRef space_ref = CFArrayGetValueAtIndex(spaces_ref, j);
74 | CFNumberRef sid_ref = CFDictionaryGetValue(space_ref, CFSTR("id64"));
75 | CFNumberGetValue(sid_ref, CFNumberGetType(sid_ref), &space_list[j]);
76 | }
77 | }
78 |
79 | CFRelease(display_spaces_ref);
80 | CFRelease(uuid);
81 |
82 | return space_list;
83 | }
84 |
85 | int display_arrangement(uint32_t did)
86 | {
87 | CFStringRef uuid = display_uuid(did);
88 | if (!uuid) return 0;
89 |
90 | CFArrayRef displays = SLSCopyManagedDisplays(g_connection);
91 | if (!displays) return 0;
92 |
93 | int result = 0;
94 | int displays_count = CFArrayGetCount(displays);
95 |
96 | for (int i = 0; i < displays_count; ++i) {
97 | if (CFEqual(CFArrayGetValueAtIndex(displays, i), uuid)) {
98 | result = i + 1;
99 | break;
100 | }
101 | }
102 |
103 | CFRelease(displays);
104 | CFRelease(uuid);
105 | return result;
106 | }
107 |
--------------------------------------------------------------------------------
/src/display.h:
--------------------------------------------------------------------------------
1 | #ifndef DISPLAY_H
2 | #define DISPLAY_H
3 |
4 | extern int SLSGetSpaceManagementMode(int cid);
5 | extern CFArrayRef SLSCopyManagedDisplaySpaces(int cid);
6 | extern CGError SLSProcessAssignToSpace(int cid, pid_t pid, uint64_t sid);
7 | extern CGError SLSProcessAssignToAllSpaces(int cid, pid_t pid);
8 | extern void SLSMoveWindowsToManagedSpace(int cid, CFArrayRef window_list, uint64_t sid);
9 | extern CGError CoreDockSendNotification(CFStringRef notification, int unknown);
10 |
11 | #define DISPLAY_EVENT_HANDLER(name) void name(uint32_t did, CGDisplayChangeSummaryFlags flags, void *context)
12 | typedef DISPLAY_EVENT_HANDLER(display_callback);
13 |
14 | extern CFUUIDRef CGDisplayCreateUUIDFromDisplayID(uint32_t did);
15 | extern CFArrayRef SLSCopyManagedDisplays(int cid);
16 | extern uint64_t SLSManagedDisplayGetCurrentSpace(int cid, CFStringRef uuid);
17 |
18 | CFStringRef display_uuid(uint32_t did);
19 | CGRect display_bounds(uint32_t did);
20 | uint64_t display_space_id(uint32_t did);
21 | uint64_t *display_space_list(uint32_t did, int *count);
22 | int display_arrangement(uint32_t did);
23 |
24 | #endif
25 |
--------------------------------------------------------------------------------
/src/display_manager.c:
--------------------------------------------------------------------------------
1 | #include "display_manager.h"
2 |
3 | extern struct window_manager g_window_manager;
4 | extern int g_connection;
5 |
6 | uint32_t display_manager_main_display_id(void)
7 | {
8 | return CGMainDisplayID();
9 | }
10 |
11 | CFStringRef display_manager_active_display_uuid(void)
12 | {
13 | return SLSCopyActiveMenuBarDisplayIdentifier(g_connection);
14 | }
15 |
16 | uint32_t display_manager_active_display_id(void)
17 | {
18 | uint32_t result = 0;
19 | CFStringRef uuid = display_manager_active_display_uuid();
20 | CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, uuid);
21 | result = CGDisplayGetDisplayIDFromUUID(uuid_ref);
22 | CFRelease(uuid_ref);
23 | CFRelease(uuid);
24 | return result;
25 | }
26 |
27 | CFStringRef display_manager_dock_display_uuid(void)
28 | {
29 | CGRect dock = display_manager_dock_rect();
30 | return SLSCopyBestManagedDisplayForRect(g_connection, dock);
31 | }
32 |
33 | uint32_t display_manager_dock_display_id(void)
34 | {
35 | CFStringRef uuid = display_manager_dock_display_uuid();
36 | if (!uuid) return 0;
37 |
38 | CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, uuid);
39 | uint32_t result = CGDisplayGetDisplayIDFromUUID(uuid_ref);
40 | CFRelease(uuid_ref);
41 | CFRelease(uuid);
42 | return result;
43 | }
44 |
45 | CFStringRef display_manager_cursor_display_uuid(void)
46 | {
47 | CGPoint cursor;
48 | SLSGetCurrentCursorLocation(g_connection, &cursor);
49 | return SLSCopyBestManagedDisplayForPoint(g_connection, cursor);
50 | }
51 |
52 | uint32_t display_manager_cursor_display_id(void)
53 | {
54 | CFStringRef uuid = display_manager_cursor_display_uuid();
55 | if (!uuid) return 0;
56 |
57 | CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, uuid);
58 | uint32_t result = CGDisplayGetDisplayIDFromUUID(uuid_ref);
59 | CFRelease(uuid_ref);
60 | CFRelease(uuid);
61 | return result;
62 | }
63 |
64 | CFStringRef display_manager_arrangement_display_uuid(int arrangement)
65 | {
66 | CFStringRef result = NULL;
67 | CFArrayRef displays = SLSCopyManagedDisplays(g_connection);
68 |
69 | int displays_count = CFArrayGetCount(displays);
70 | for (int i = 0; i < displays_count; ++i) {
71 | if ((i+1) != arrangement) continue;
72 | result = CFRetain(CFArrayGetValueAtIndex(displays, i));
73 | break;
74 | }
75 |
76 | CFRelease(displays);
77 | return result;
78 | }
79 |
80 | uint32_t display_manager_arrangement_display_id(int arrangement)
81 | {
82 | uint32_t result = 0;
83 | CFArrayRef displays = SLSCopyManagedDisplays(g_connection);
84 |
85 | int displays_count = CFArrayGetCount(displays);
86 | for (int i = 0; i < displays_count; ++i) {
87 | if ((i+1) != arrangement) continue;
88 | CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, CFArrayGetValueAtIndex(displays, i));
89 | result = CGDisplayGetDisplayIDFromUUID(uuid_ref);
90 | CFRelease(uuid_ref);
91 | break;
92 | }
93 |
94 | CFRelease(displays);
95 | return result;
96 | }
97 |
98 | uint32_t display_manager_first_display_id(void)
99 | {
100 | return display_manager_arrangement_display_id(1);
101 | }
102 |
103 | uint32_t display_manager_last_display_id(void)
104 | {
105 | int arrangement = display_manager_active_display_count();
106 | return display_manager_arrangement_display_id(arrangement);
107 | }
108 |
109 | bool display_manager_menu_bar_hidden(void)
110 | {
111 | int status = 0;
112 | SLSGetMenuBarAutohideEnabled(g_connection, &status);
113 | return status;
114 | }
115 |
116 | CGRect display_manager_menu_bar_rect(uint32_t did)
117 | {
118 | CGRect bounds = {};
119 | SLSGetRevealedMenuBarBounds(&bounds, g_connection, display_space_id(did));
120 | return bounds;
121 | }
122 |
123 | bool display_manager_dock_hidden(void)
124 | {
125 | return CoreDockGetAutoHideEnabled();
126 | }
127 |
128 | int display_manager_dock_orientation(void)
129 | {
130 | int pinning = 0;
131 | int orientation = 0;
132 | CoreDockGetOrientationAndPinning(&orientation, &pinning);
133 | return orientation;
134 | }
135 |
136 | CGRect display_manager_dock_rect(void)
137 | {
138 | int reason = 0;
139 | CGRect bounds = {};
140 | SLSGetDockRectWithReason(g_connection, &bounds, &reason);
141 | return bounds;
142 | }
143 |
144 | bool display_manager_active_display_is_animating(void)
145 | {
146 | CFStringRef uuid = display_manager_active_display_uuid();
147 | bool result = SLSManagedDisplayIsAnimating(g_connection, uuid);
148 | CFRelease(uuid);
149 | return result;
150 | }
151 |
152 | bool display_manager_display_is_animating(uint32_t did)
153 | {
154 | CFStringRef uuid = display_uuid(did);
155 | if (!uuid) return false;
156 |
157 | bool result = SLSManagedDisplayIsAnimating(g_connection, uuid);
158 | CFRelease(uuid);
159 | return result;
160 | }
161 |
162 | uint32_t display_manager_active_display_count(void)
163 | {
164 | uint32_t count;
165 | CGGetActiveDisplayList(0, NULL, &count);
166 | return count;
167 | }
168 |
169 | uint32_t *display_manager_active_display_list(uint32_t *count)
170 | {
171 | int display_count = display_manager_active_display_count();
172 | uint32_t *result = malloc(sizeof(uint32_t) * display_count);
173 | CGGetActiveDisplayList(display_count, result, count);
174 | return result;
175 | }
176 |
177 | bool display_manager_begin(struct display_manager *dm)
178 | {
179 | dm->current_display_id = display_manager_active_display_id();
180 | dm->last_display_id = dm->current_display_id;
181 | return CGDisplayRegisterReconfigurationCallback(display_handler, NULL) == kCGErrorSuccess;
182 | }
183 |
184 | bool display_manager_end(void)
185 | {
186 | return CGDisplayRemoveReconfigurationCallback(display_handler, NULL) == kCGErrorSuccess;
187 | }
188 |
--------------------------------------------------------------------------------
/src/display_manager.h:
--------------------------------------------------------------------------------
1 | #ifndef DISPLAY_MANAGER_H
2 | #define DISPLAY_MANAGER_H
3 |
4 | extern CFStringRef SLSCopyActiveMenuBarDisplayIdentifier(int cid);
5 | extern CFStringRef SLSCopyBestManagedDisplayForPoint(int cid, CGPoint point);
6 | extern bool SLSManagedDisplayIsAnimating(int cid, CFStringRef uuid);
7 | extern CGError SLSGetMenuBarAutohideEnabled(int cid, int *enabled);
8 | extern CGError SLSGetRevealedMenuBarBounds(CGRect *rect, int cid, uint64_t sid);
9 | extern CGError SLSGetDockRectWithReason(int cid, CGRect *rect, int *reason);
10 | extern Boolean CoreDockGetAutoHideEnabled(void);
11 | extern void CoreDockGetOrientationAndPinning(int *orientation, int *pinning);
12 |
13 | #define DOCK_ORIENTATION_BOTTOM 2
14 | #define DOCK_ORIENTATION_LEFT 3
15 | #define DOCK_ORIENTATION_RIGHT 4
16 |
17 | struct display_manager
18 | {
19 | uint32_t current_display_id;
20 | uint32_t last_display_id;
21 | };
22 |
23 | uint32_t display_manager_main_display_id(void);
24 | CFStringRef display_manager_active_display_uuid(void);
25 | uint32_t display_manager_active_display_id(void);
26 | CFStringRef display_manager_dock_display_uuid(void);
27 | uint32_t display_manager_dock_display_id(void);
28 | CFStringRef display_manager_cursor_display_uuid(void);
29 | uint32_t display_manager_cursor_display_id(void);
30 | CFStringRef display_manager_arrangement_display_uuid(int arrangement);
31 | uint32_t display_manager_arrangement_display_id(int arrangement);
32 | uint32_t display_manager_prev_display_id(uint32_t did);
33 | uint32_t display_manager_next_display_id(uint32_t did);
34 | uint32_t display_manager_first_display_id(void);
35 | uint32_t display_manager_last_display_id(void);
36 | bool display_manager_menu_bar_hidden(void);
37 | CGRect display_manager_menu_bar_rect(uint32_t did);
38 | bool display_manager_dock_hidden(void);
39 | int display_manager_dock_orientation(void);
40 | CGRect display_manager_dock_rect(void);
41 | bool display_manager_active_display_is_animating(void);
42 | bool display_manager_display_is_animating(uint32_t did);
43 | uint32_t display_manager_active_display_count(void);
44 | uint32_t *display_manager_active_display_list(uint32_t *count);
45 | //void display_manager_focus_display(uint32_t did);
46 | bool display_manager_begin(struct display_manager *dm);
47 | bool display_manager_end(void);
48 |
49 | #endif
50 |
--------------------------------------------------------------------------------
/src/event.c:
--------------------------------------------------------------------------------
1 | #include "event.h"
2 |
3 | extern struct event_loop g_event_loop;
4 | extern struct process_manager g_process_manager;
5 | extern struct display_manager g_display_manager;
6 | extern struct bar_manager g_bar_manager;
7 | extern struct application_manager g_application_manager;
8 | extern bool g_mission_control_active;
9 | extern int g_connection;
10 |
11 | enum event_type event_type_from_string(const char *str)
12 | {
13 | for (int i = EVENT_TYPE_UNKNOWN + 1; i < EVENT_TYPE_COUNT; ++i) {
14 | if (string_equals(str, event_type_str[i])) return i;
15 | }
16 |
17 | return EVENT_TYPE_UNKNOWN;
18 | }
19 |
20 | struct event *event_create(struct event_loop *event_loop, enum event_type type, void *context)
21 | {
22 | struct event *event = memory_pool_push(&event_loop->pool, struct event);
23 | event->type = type;
24 | event->context = context;
25 | event->param1 = 0;
26 | event->info = 0;
27 | #ifdef DEBUG
28 | uint64_t count = __sync_add_and_fetch(&event_loop->count, 1);
29 | assert(count > 0 && count < EVENT_MAX_COUNT);
30 | #endif
31 | return event;
32 | }
33 |
34 | struct event *event_create_p1(struct event_loop *event_loop, enum event_type type, void *context, int param1)
35 | {
36 | struct event *event = memory_pool_push(&event_loop->pool, struct event);
37 | event->type = type;
38 | event->context = context;
39 | event->param1 = param1;
40 | event->info = 0;
41 | #ifdef DEBUG
42 | uint64_t count = __sync_add_and_fetch(&event_loop->count, 1);
43 | assert(count > 0 && count < EVENT_MAX_COUNT);
44 | #endif
45 | return event;
46 | }
47 |
48 | void event_destroy(struct event_loop *event_loop, struct event *event)
49 | {
50 | switch (event->type) {
51 | default: break;
52 | case APPLICATION_TERMINATED: {
53 | process_destroy(event->context);
54 | } break;
55 | }
56 |
57 | #ifdef DEBUG
58 | uint64_t count = __sync_sub_and_fetch(&event_loop->count, 1);
59 | assert(count >= 0 && count < EVENT_MAX_COUNT);
60 | #endif
61 | }
62 |
63 |
64 | static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_LAUNCHED)
65 | {
66 | struct process *process = context;
67 | debug("%s: %s\n", __FUNCTION__, process->name);
68 |
69 | if ((process->terminated) || (kill(process->pid, 0) == -1)) {
70 | debug("%s: %s terminated during launch\n", __FUNCTION__, process->name);
71 | return EVENT_FAILURE;
72 | }
73 |
74 | struct application *application = application_create(process);
75 | if (application_observe(application)) {
76 | application_manager_add_application(&g_application_manager, application);
77 |
78 | return EVENT_SUCCESS;
79 | } else {
80 | bool retry_ax = application->retry;
81 | application_unobserve(application);
82 | application_destroy(application);
83 | debug("%s: could not observe %s (%d)\n", __FUNCTION__, process->name, retry_ax);
84 |
85 | if (retry_ax) {
86 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.01f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
87 | struct event *event = event_create(&g_event_loop, APPLICATION_LAUNCHED, process);
88 | event_loop_post(&g_event_loop, event);
89 | });
90 | }
91 |
92 | return EVENT_FAILURE;
93 | }
94 | }
95 |
96 | static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_TERMINATED)
97 | {
98 | struct process *process = context;
99 | struct application *application = application_manager_find_application(&g_application_manager, process->pid);
100 |
101 | if (!application) {
102 | debug("%s: %s (not observed)\n", __FUNCTION__, process->name);
103 | return EVENT_FAILURE;
104 | }
105 |
106 | debug("%s: %s\n", __FUNCTION__, process->name);
107 | application_manager_remove_application(&g_application_manager, application->pid);
108 |
109 | application_unobserve(application);
110 | application_destroy(application);
111 |
112 | return EVENT_SUCCESS;
113 | }
114 |
115 | static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_FRONT_SWITCHED)
116 | {
117 | debug("%s\n", __FUNCTION__);
118 | bar_manager_refresh(&g_bar_manager);
119 |
120 | return EVENT_SUCCESS;
121 | }
122 |
123 | static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_FOCUSED)
124 | {
125 | debug("%s\n", __FUNCTION__);
126 | bar_manager_refresh(&g_bar_manager);
127 |
128 | return EVENT_SUCCESS;
129 | }
130 |
131 | static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_TITLE_CHANGED)
132 | {
133 | debug("%s\n", __FUNCTION__);
134 |
135 | // TODO: we can optimize by checking if it the focused window
136 | bar_manager_refresh(&g_bar_manager);
137 |
138 | return EVENT_SUCCESS;
139 | }
140 |
141 | static EVENT_CALLBACK(EVENT_HANDLER_SPACE_CHANGED)
142 | {
143 | debug("%s\n", __FUNCTION__);
144 |
145 | bar_manager_refresh(&g_bar_manager);
146 |
147 | return EVENT_SUCCESS;
148 | }
149 |
150 | static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_CHANGED)
151 | {
152 | g_display_manager.last_display_id = g_display_manager.current_display_id;
153 | g_display_manager.current_display_id = display_manager_active_display_id();
154 |
155 | debug("%s: %d\n", __FUNCTION__, g_display_manager.current_display_id);
156 |
157 | bar_manager_refresh(&g_bar_manager);
158 |
159 | return EVENT_SUCCESS;
160 | }
161 |
162 | static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_ADDED)
163 | {
164 | uint32_t did = (uint32_t)(intptr_t) context;
165 | debug("%s: %d\n", __FUNCTION__, did);
166 | bar_manager_display_changed(&g_bar_manager);
167 | return EVENT_SUCCESS;
168 | }
169 |
170 | static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_REMOVED)
171 | {
172 | uint32_t did = (uint32_t)(intptr_t) context;
173 | debug("%s: %d\n", __FUNCTION__, did);
174 | bar_manager_display_changed(&g_bar_manager);
175 | return EVENT_SUCCESS;
176 | }
177 |
178 | static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_MOVED)
179 | {
180 | uint32_t did = (uint32_t)(intptr_t) context;
181 | debug("%s: %d\n", __FUNCTION__, did);
182 | bar_manager_display_changed(&g_bar_manager);
183 | return EVENT_SUCCESS;
184 | }
185 |
186 | static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_RESIZED)
187 | {
188 | uint32_t did = (uint32_t)(intptr_t) context;
189 | debug("%s: %d\n", __FUNCTION__, did);
190 | bar_manager_display_changed(&g_bar_manager);
191 | return EVENT_SUCCESS;
192 | }
193 |
194 | static EVENT_CALLBACK(EVENT_HANDLER_MENU_BAR_HIDDEN_CHANGED)
195 | {
196 | debug("%s:\n", __FUNCTION__);
197 | bar_manager_resize(&g_bar_manager);
198 | return EVENT_SUCCESS;
199 | }
200 |
201 | static EVENT_CALLBACK(EVENT_HANDLER_SYSTEM_WOKE)
202 | {
203 | debug("%s:\n", __FUNCTION__);
204 | bar_manager_refresh(&g_bar_manager);
205 | return EVENT_SUCCESS;
206 | }
207 |
208 | static EVENT_CALLBACK(EVENT_HANDLER_BAR_REFRESH)
209 | {
210 | bar_manager_refresh(&g_bar_manager);
211 | return EVENT_SUCCESS;
212 | }
213 |
214 | static EVENT_CALLBACK(EVENT_HANDLER_SHELL_REFRESH)
215 | {
216 | set_shell_outputs(&g_bar_manager);
217 | return EVENT_SUCCESS;
218 | }
219 |
220 | static EVENT_CALLBACK(EVENT_HANDLER_DAEMON_MESSAGE)
221 | {
222 | FILE *rsp = fdopen(param1, "w");
223 | if (!rsp) goto out;
224 |
225 | if (g_verbose) {
226 | fprintf(stdout, "%s:", __FUNCTION__);
227 | for (char *message = context; *message;) {
228 | message += fprintf(stdout, " %s", message);
229 | }
230 | putc('\n', stdout);
231 | fflush(stdout);
232 | }
233 |
234 | handle_message(rsp, context);
235 | fflush(rsp);
236 | fclose(rsp);
237 |
238 | out:
239 | socket_close(param1);
240 | free(context);
241 |
242 | return EVENT_SUCCESS;
243 | }
244 |
--------------------------------------------------------------------------------
/src/event.h:
--------------------------------------------------------------------------------
1 | #ifndef EVENT_LOOP_EVENT_H
2 | #define EVENT_LOOP_EVENT_H
3 |
4 | #define EVENT_CALLBACK(name) uint32_t name(void *context, int param1)
5 | typedef EVENT_CALLBACK(event_callback);
6 |
7 | static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_LAUNCHED);
8 | static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_TERMINATED);
9 | static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_FRONT_SWITCHED);
10 | static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_FOCUSED);
11 | static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_TITLE_CHANGED);
12 | static EVENT_CALLBACK(EVENT_HANDLER_SPACE_CHANGED);
13 | static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_ADDED);
14 | static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_REMOVED);
15 | static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_MOVED);
16 | static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_RESIZED);
17 | static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_CHANGED);
18 | static EVENT_CALLBACK(EVENT_HANDLER_MENU_BAR_HIDDEN_CHANGED);
19 | static EVENT_CALLBACK(EVENT_HANDLER_SYSTEM_WOKE);
20 | static EVENT_CALLBACK(EVENT_HANDLER_BAR_REFRESH);
21 | static EVENT_CALLBACK(EVENT_HANDLER_SHELL_REFRESH);
22 | static EVENT_CALLBACK(EVENT_HANDLER_DAEMON_MESSAGE);
23 |
24 | #define EVENT_QUEUED 0x0
25 | #define EVENT_PROCESSED 0x1
26 |
27 | #define EVENT_SUCCESS 0x0
28 | #define EVENT_FAILURE 0x1
29 | #define EVENT_MOUSE_IGNORE 0x2
30 |
31 | #define event_status(e) ((e) & 0x1)
32 | #define event_result(e) ((e) >> 0x1)
33 |
34 | enum event_type
35 | {
36 | EVENT_TYPE_UNKNOWN,
37 | APPLICATION_LAUNCHED,
38 | APPLICATION_TERMINATED,
39 | APPLICATION_FRONT_SWITCHED,
40 | WINDOW_FOCUSED,
41 | WINDOW_TITLE_CHANGED,
42 | SPACE_CHANGED,
43 | DISPLAY_ADDED,
44 | DISPLAY_REMOVED,
45 | DISPLAY_MOVED,
46 | DISPLAY_RESIZED,
47 | DISPLAY_CHANGED,
48 | MENU_BAR_HIDDEN_CHANGED,
49 | SYSTEM_WOKE,
50 | BAR_REFRESH,
51 | SHELL_REFRESH,
52 | DAEMON_MESSAGE,
53 |
54 | EVENT_TYPE_COUNT
55 | };
56 |
57 | static const char *event_type_str[] =
58 | {
59 | [EVENT_TYPE_UNKNOWN] = "event_type_unknown",
60 |
61 | [APPLICATION_LAUNCHED] = "application_launched",
62 | [APPLICATION_TERMINATED] = "application_terminated",
63 | [APPLICATION_FRONT_SWITCHED] = "application_front_switched",
64 | [WINDOW_FOCUSED] = "window_focused",
65 | [WINDOW_TITLE_CHANGED] = "window_title_changed",
66 | [SPACE_CHANGED] = "space_changed",
67 | [DISPLAY_ADDED] = "display_added",
68 | [DISPLAY_REMOVED] = "display_removed",
69 | [DISPLAY_MOVED] = "display_moved",
70 | [DISPLAY_RESIZED] = "display_resized",
71 | [DISPLAY_CHANGED] = "display_changed",
72 | [MENU_BAR_HIDDEN_CHANGED] = "menu_bar_hidden_changed",
73 | [SYSTEM_WOKE] = "system_woke",
74 | [BAR_REFRESH] = "bar_refresh",
75 | [SHELL_REFRESH] = "shell_refresh",
76 | [DAEMON_MESSAGE] = "daemon_message",
77 |
78 | [EVENT_TYPE_COUNT] = "event_type_count"
79 | };
80 |
81 | static event_callback *event_handler[] =
82 | {
83 | [APPLICATION_LAUNCHED] = EVENT_HANDLER_APPLICATION_LAUNCHED,
84 | [APPLICATION_TERMINATED] = EVENT_HANDLER_APPLICATION_TERMINATED,
85 | [APPLICATION_FRONT_SWITCHED] = EVENT_HANDLER_APPLICATION_FRONT_SWITCHED,
86 | [WINDOW_FOCUSED] = EVENT_HANDLER_WINDOW_FOCUSED,
87 | [WINDOW_TITLE_CHANGED] = EVENT_HANDLER_WINDOW_TITLE_CHANGED,
88 | [SPACE_CHANGED] = EVENT_HANDLER_SPACE_CHANGED,
89 | [DISPLAY_ADDED] = EVENT_HANDLER_DISPLAY_ADDED,
90 | [DISPLAY_REMOVED] = EVENT_HANDLER_DISPLAY_REMOVED,
91 | [DISPLAY_MOVED] = EVENT_HANDLER_DISPLAY_MOVED,
92 | [DISPLAY_RESIZED] = EVENT_HANDLER_DISPLAY_RESIZED,
93 | [DISPLAY_CHANGED] = EVENT_HANDLER_DISPLAY_CHANGED,
94 | [MENU_BAR_HIDDEN_CHANGED] = EVENT_HANDLER_MENU_BAR_HIDDEN_CHANGED,
95 | [SYSTEM_WOKE] = EVENT_HANDLER_SYSTEM_WOKE,
96 | [BAR_REFRESH] = EVENT_HANDLER_BAR_REFRESH,
97 | [SHELL_REFRESH] = EVENT_HANDLER_SHELL_REFRESH,
98 | [DAEMON_MESSAGE] = EVENT_HANDLER_DAEMON_MESSAGE,
99 | };
100 |
101 | struct event
102 | {
103 | void *context;
104 | volatile uint32_t *info;
105 | enum event_type type;
106 | int param1;
107 | };
108 |
109 | struct event *event_create(struct event_loop *event_loop, enum event_type type, void *context);
110 | struct event *event_create_p1(struct event_loop *event_loop, enum event_type type, void *context, int param1);
111 | void event_destroy(struct event_loop *event_loop, struct event *event);
112 | enum event_type event_type_from_string(const char *str);
113 |
114 | #endif
115 |
--------------------------------------------------------------------------------
/src/event_loop.c:
--------------------------------------------------------------------------------
1 | #include "event_loop.h"
2 |
3 | #ifdef STATS
4 | struct cycle_counter
5 | {
6 | uint64_t cycle_count;
7 | uint64_t hit_count;
8 | };
9 |
10 | static struct cycle_counter queue_counters[2];
11 | static struct cycle_counter event_counters[EVENT_TYPE_COUNT];
12 |
13 | static inline void cycle_counter_tick(const char *name, struct cycle_counter *counter, uint64_t elapsed_cycles)
14 | {
15 | uint64_t cycle_count = __sync_add_and_fetch(&counter->cycle_count, elapsed_cycles);
16 | uint64_t hit_count = __sync_add_and_fetch(&counter->hit_count, 1);
17 | fprintf(stdout, "%30s: hits %'25lld | cur %'25lld | avg %'25lld\n",
18 | name, hit_count, elapsed_cycles, cycle_count / hit_count)
19 | }
20 | #endif
21 |
22 | static bool queue_init(struct queue *queue)
23 | {
24 | if (!memory_pool_init(&queue->pool, QUEUE_POOL_SIZE)) return false;
25 | queue->head = memory_pool_push(&queue->pool, struct queue_item);
26 | queue->head->data = NULL;
27 | queue->head->next = NULL;
28 | queue->tail = queue->head;
29 | #ifdef DEBUG
30 | queue->count = 0;
31 | #endif
32 | return true;
33 | };
34 |
35 | static void queue_push(struct queue *queue, struct event *event)
36 | {
37 | bool success;
38 | struct queue_item *tail, *new_tail;
39 |
40 | #ifdef STATS
41 | uint64_t begin_cycles = __rdtsc();
42 | #endif
43 |
44 | new_tail = memory_pool_push(&queue->pool, struct queue_item);
45 | new_tail->data = event;
46 | new_tail->next = NULL;
47 | __asm__ __volatile__ ("" ::: "memory");
48 |
49 | do {
50 | tail = queue->tail;
51 | success = __sync_bool_compare_and_swap(&tail->next, NULL, new_tail);
52 | if (!success) __sync_bool_compare_and_swap(&queue->tail, tail, tail->next);
53 | } while (!success);
54 | __sync_bool_compare_and_swap(&queue->tail, tail, new_tail);
55 |
56 | #ifdef DEBUG
57 | uint64_t count = __sync_add_and_fetch(&queue->count, 1);
58 | assert(count > 0 && count < QUEUE_MAX_COUNT);
59 | #endif
60 |
61 | #ifdef STATS
62 | cycle_counter_tick(__FUNCTION__, &queue_counters[0], __rdtsc() - begin_cycles);
63 | #endif
64 | }
65 |
66 | static struct event *queue_pop(struct queue *queue)
67 | {
68 | struct queue_item *head;
69 |
70 | #ifdef STATS
71 | uint64_t begin_cycles = __rdtsc();
72 | #endif
73 |
74 | do {
75 | head = queue->head;
76 | if (!head->next) return NULL;
77 | } while (!__sync_bool_compare_and_swap(&queue->head, head, head->next));
78 |
79 | #ifdef DEBUG
80 | uint64_t count = __sync_sub_and_fetch(&queue->count, 1);
81 | assert(count >= 0 && count < QUEUE_MAX_COUNT);
82 | #endif
83 |
84 | #ifdef STATS
85 | cycle_counter_tick(__FUNCTION__, &queue_counters[1], __rdtsc() - begin_cycles);
86 | #endif
87 |
88 | return head->next->data;
89 | }
90 |
91 | static void *event_loop_run(void *context)
92 | {
93 | struct event_loop *event_loop = (struct event_loop *) context;
94 | struct queue *queue = (struct queue *) &event_loop->queue;
95 |
96 | while (event_loop->is_running) {
97 | struct event *event = queue_pop(queue);
98 | if (event) {
99 | #ifdef STATS
100 | uint64_t begin_cycles = __rdtsc();
101 | #endif
102 | uint32_t result = event_handler[event->type](event->context, event->param1);
103 | #ifdef STATS
104 | cycle_counter_tick(event_type_str[event->type], &event_counters[event->type], __rdtsc() - begin_cycles);
105 | #endif
106 | if (event->info) *event->info = (result << 0x1) | EVENT_PROCESSED;
107 |
108 | event_destroy(event_loop, event);
109 | } else {
110 | sem_wait(event_loop->semaphore);
111 | }
112 | }
113 |
114 | return NULL;
115 | }
116 |
117 | void event_loop_post(struct event_loop *event_loop, struct event *event)
118 | {
119 | assert(event_loop->is_running);
120 | queue_push(&event_loop->queue, event);
121 | sem_post(event_loop->semaphore);
122 | }
123 |
124 | bool event_loop_init(struct event_loop *event_loop)
125 | {
126 | if (!queue_init(&event_loop->queue)) return false;
127 | if (!memory_pool_init(&event_loop->pool, EVENT_POOL_SIZE)) return false;
128 | event_loop->is_running = false;
129 | #ifdef DEBUG
130 | event_loop->count = 0;
131 | #endif
132 | #ifdef STATS
133 | setlocale(LC_ALL, ""); // For fprintf digit grouping
134 | #endif
135 | event_loop->semaphore = sem_open("spacebar_event_loop_semaphore", O_CREAT, 0600, 0);
136 | sem_unlink("spacebar_event_loop_semaphore");
137 | return event_loop->semaphore != SEM_FAILED;
138 | }
139 |
140 | bool event_loop_begin(struct event_loop *event_loop)
141 | {
142 | if (event_loop->is_running) return false;
143 | event_loop->is_running = true;
144 | pthread_create(&event_loop->thread, NULL, &event_loop_run, event_loop);
145 | return true;
146 | }
147 |
148 | bool event_loop_end(struct event_loop *event_loop)
149 | {
150 | if (!event_loop->is_running) return false;
151 | event_loop->is_running = false;
152 | pthread_join(event_loop->thread, NULL);
153 | return true;
154 | }
155 |
--------------------------------------------------------------------------------
/src/event_loop.h:
--------------------------------------------------------------------------------
1 | #ifndef EVENT_LOOP_H
2 | #define EVENT_LOOP_H
3 |
4 | #define EVENT_POOL_SIZE KILOBYTES(36)
5 | #define EVENT_MAX_COUNT ((EVENT_POOL_SIZE) / (sizeof(struct event)))
6 |
7 | #define QUEUE_POOL_SIZE KILOBYTES(16)
8 | #define QUEUE_MAX_COUNT ((QUEUE_POOL_SIZE) / (sizeof(struct queue_item)))
9 |
10 | struct queue_item
11 | {
12 | struct event *data;
13 | struct queue_item *next;
14 | };
15 |
16 | struct queue
17 | {
18 | struct memory_pool pool;
19 | struct queue_item *head;
20 | struct queue_item *tail;
21 | #ifdef DEBUG
22 | volatile uint64_t count;
23 | #endif
24 | };
25 |
26 | struct event_loop
27 | {
28 | bool is_running;
29 | pthread_t thread;
30 | sem_t *semaphore;
31 | struct queue queue;
32 | struct memory_pool pool;
33 | #ifdef DEBUG
34 | volatile uint64_t count;
35 | #endif
36 | };
37 |
38 | bool event_loop_init(struct event_loop *event_loop);
39 | bool event_loop_begin(struct event_loop *event_loop);
40 | bool event_loop_end(struct event_loop *event_loop);
41 | void event_loop_post(struct event_loop *event_loop, struct event *event);
42 |
43 | #endif
44 |
--------------------------------------------------------------------------------
/src/manifest.m:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #include "misc/timing.h"
26 | #include "misc/macros.h"
27 | #include "misc/notify.h"
28 | #include "misc/log.h"
29 | #include "misc/helpers.h"
30 | #include "misc/memory_pool.h"
31 | #include "misc/sbuffer.h"
32 | #define HASHTABLE_IMPLEMENTATION
33 | #include "misc/hashtable.h"
34 | #undef HASHTABLE_IMPLEMENTATION
35 | #include "misc/socket.h"
36 | #include "misc/socket.c"
37 | #include "misc/dnd.c"
38 |
39 | //#include "osax/sa.h"
40 | //#include "osax/sa_loader.c"
41 | //#include "osax/sa_payload.c"
42 | //#include "osax/sa.m"
43 |
44 | #include "event_loop.h"
45 | #include "event.h"
46 | #include "workspace.h"
47 | #include "message.h"
48 | #include "display.h"
49 | #include "process_manager.h"
50 | #include "application.h"
51 | #include "display_manager.h"
52 | #include "application_manager.h"
53 | #include "bar.h"
54 | #include "bar_manager.h"
55 |
56 | #include "event_loop.c"
57 | #include "event.c"
58 | #include "workspace.m"
59 | #include "message.c"
60 | #include "display.c"
61 | #include "process_manager.c"
62 | #include "application.c"
63 | #include "display_manager.c"
64 | #include "bar.c"
65 | #include "bar_manager.c"
66 | #include "application_manager.c"
67 |
68 | #include "spacebar.c"
69 |
--------------------------------------------------------------------------------
/src/message.h:
--------------------------------------------------------------------------------
1 | #ifndef MESSAGE_H
2 | #define MESSAGE_H
3 |
4 | struct token
5 | {
6 | char *text;
7 | unsigned int length;
8 | };
9 |
10 | static SOCKET_DAEMON_HANDLER(message_handler);
11 | void handle_message(FILE *rsp, char *message);
12 |
13 | #endif
14 |
--------------------------------------------------------------------------------
/src/misc/dnd.c:
--------------------------------------------------------------------------------
1 | static bool getDoNotDisturb() {
2 | bool doNotDisturb;
3 | NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
4 | bool isBigSur = version.majorVersion == 11 || (version.majorVersion == 10 && version.minorVersion > 15);
5 | if (isBigSur) {
6 | // On big sur we have to read a plist from a plist...
7 | NSData* dndData = [[[NSUserDefaults alloc] initWithSuiteName:@"com.apple.ncprefs"] dataForKey:@"dnd_prefs"];
8 | // If there is no DND data let's assume that we aren't in DND
9 | if (!dndData) return false;
10 |
11 | NSDictionary* dndDict = [NSPropertyListSerialization
12 | propertyListWithData:dndData
13 | options:NSPropertyListImmutable
14 | format:nil
15 | error:nil];
16 | // If the dnd data isn't a valid plist, again assume we aren't in DND
17 | if (!dndDict) return false;
18 |
19 | NSDictionary* userPrefs = [dndDict valueForKey:@"userPref"];
20 | if (userPrefs) {
21 | NSNumber* dndEnabled = [userPrefs valueForKey:@"enabled"];
22 | // If the user pref has it set to enabled
23 | if ([dndEnabled intValue] == 1) return true;
24 | }
25 |
26 | NSDictionary* scheduledPrefs = [dndDict valueForKey:@"scheduledTime"];
27 | if (scheduledPrefs) {
28 | NSNumber* scheduleEnabled = [scheduledPrefs valueForKey:@"enabled"];
29 | NSNumber* start = [scheduledPrefs valueForKey:@"start"];
30 | NSNumber* end = [scheduledPrefs valueForKey:@"end"];
31 | // If the schedule is enabled, we need to manually determine if we fall in the start / end interval
32 | if ([scheduleEnabled intValue] == 1 && start && end) {
33 | NSDate* now = [NSDate date];
34 | NSCalendar *calendar = [NSCalendar currentCalendar];
35 | NSDateComponents *components = [calendar components:(NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:now];
36 | NSInteger hour = [components hour];
37 | NSInteger minute = [components minute];
38 | NSInteger current = (hour * 60) + minute;
39 |
40 | NSInteger startInt = [start intValue];
41 | NSInteger endInt = [end intValue];
42 | // Normal way round, start is before the end
43 | if (startInt < endInt) {
44 | // Start is inclusive, end is exclusive
45 | if (current >= startInt && current < endInt) return true;
46 | } else if (endInt < startInt) {
47 | // The end can also be _after_ the start making the DND interval loop over midnight
48 | if (current >= startInt) return true;
49 | if (current < endInt) return true;
50 | }
51 | }
52 | }
53 |
54 | // Not manually enabled, not enabled due to schedule
55 | return false;
56 | } else {
57 | // Older than big sur we can just read the pref directly
58 | doNotDisturb = [[[[NSUserDefaults alloc] initWithSuiteName:@"com.apple.notificationcenterui"] objectForKey:@"doNotDisturb"] boolValue];
59 | }
60 | return doNotDisturb;
61 | }
62 |
--------------------------------------------------------------------------------
/src/misc/hashtable.h:
--------------------------------------------------------------------------------
1 | #ifndef HASHTABLE_H
2 | #define HASHTABLE_H
3 |
4 | #define TABLE_HASH_FUNC(name) unsigned long name(void *key)
5 | typedef TABLE_HASH_FUNC(table_hash_func);
6 |
7 | #define TABLE_COMPARE_FUNC(name) int name(void *key_a, void *key_b)
8 | typedef TABLE_COMPARE_FUNC(table_compare_func);
9 |
10 | struct bucket
11 | {
12 | void *key;
13 | void *value;
14 | struct bucket *next;
15 | };
16 | struct table
17 | {
18 | int count;
19 | int capacity;
20 | float max_load;
21 | table_hash_func *hash;
22 | table_compare_func *cmp;
23 | struct bucket **buckets;
24 | };
25 |
26 | void table_init(struct table *table, int capacity, table_hash_func hash, table_compare_func cmp);
27 | void table_free(struct table *table);
28 |
29 | #define table_add(table, key, value) _table_add(table, key, sizeof(*key), value)
30 | void _table_add(struct table *table, void *key, int key_size, void *value);
31 | void table_remove(struct table *table, void *key);
32 | void *table_find(struct table *table, void *key);
33 |
34 | #endif
35 |
36 | #ifdef HASHTABLE_IMPLEMENTATION
37 | void table_init(struct table *table, int capacity, table_hash_func hash, table_compare_func cmp)
38 | {
39 | table->count = 0;
40 | table->capacity = capacity;
41 | table->max_load = 0.75f;
42 | table->hash = hash;
43 | table->cmp = cmp;
44 | table->buckets = malloc(sizeof(struct bucket *) * capacity);
45 | memset(table->buckets, 0, sizeof(struct bucket *) * capacity);
46 | }
47 |
48 | void table_free(struct table *table)
49 | {
50 | for (int i = 0; i < table->capacity; ++i) {
51 | struct bucket *next, *bucket = table->buckets[i];
52 | while (bucket) {
53 | next = bucket->next;
54 | free(bucket->key);
55 | free(bucket);
56 | bucket = next;
57 | }
58 | }
59 |
60 | if (table->buckets) {
61 | free(table->buckets);
62 | table->buckets = NULL;
63 | }
64 | }
65 |
66 | static struct bucket **
67 | table_get_bucket(struct table *table, void *key)
68 | {
69 | struct bucket **bucket = table->buckets + (table->hash(key) % table->capacity);
70 | while (*bucket) {
71 | if (table->cmp((*bucket)->key, key)) {
72 | break;
73 | }
74 | bucket = &(*bucket)->next;
75 | }
76 | return bucket;
77 | }
78 |
79 | static void
80 | table_rehash(struct table *table)
81 | {
82 | struct bucket **old_buckets = table->buckets;
83 | int old_capacity = table->capacity;
84 |
85 | table->count = 0;
86 | table->capacity = 2 * table->capacity;
87 | table->buckets = malloc(sizeof(struct bucket *) * table->capacity);
88 | memset(table->buckets, 0, sizeof(struct bucket *) * table->capacity);
89 |
90 | for (int i = 0; i < old_capacity; ++i) {
91 | struct bucket *next_bucket, *old_bucket = old_buckets[i];
92 | while (old_bucket) {
93 | struct bucket **new_bucket = table_get_bucket(table, old_bucket->key);
94 | *new_bucket = malloc(sizeof(struct bucket));
95 | (*new_bucket)->key = old_bucket->key;
96 | (*new_bucket)->value = old_bucket->value;
97 | (*new_bucket)->next = NULL;
98 | ++table->count;
99 | next_bucket = old_bucket->next;
100 | free(old_bucket);
101 | old_bucket = next_bucket;
102 | }
103 | }
104 |
105 | free(old_buckets);
106 | }
107 |
108 | void _table_add(struct table *table, void *key, int key_size, void *value)
109 | {
110 | struct bucket **bucket = table_get_bucket(table, key);
111 | if (*bucket) {
112 | if (!(*bucket)->value) {
113 | (*bucket)->value = value;
114 | }
115 | } else {
116 | *bucket = malloc(sizeof(struct bucket));
117 | (*bucket)->key = malloc(key_size);
118 | (*bucket)->value = value;
119 | memcpy((*bucket)->key, key, key_size);
120 | (*bucket)->next = NULL;
121 | ++table->count;
122 |
123 | float load = (1.0f * table->count) / table->capacity;
124 | if (load > table->max_load) {
125 | table_rehash(table);
126 | }
127 | }
128 | }
129 |
130 | void table_remove(struct table *table, void *key)
131 | {
132 | struct bucket *next, **bucket = table_get_bucket(table, key);
133 | if (*bucket) {
134 | free((*bucket)->key);
135 | next = (*bucket)->next;
136 | free(*bucket);
137 | *bucket = next;
138 | --table->count;
139 | }
140 | }
141 |
142 | void *table_find(struct table *table, void *key)
143 | {
144 | struct bucket *bucket = *table_get_bucket(table, key);
145 | return bucket ? bucket->value : NULL;
146 | }
147 | #endif
148 |
--------------------------------------------------------------------------------
/src/misc/helpers.h:
--------------------------------------------------------------------------------
1 | #ifndef HELPERS_H
2 | #define HELPERS_H
3 |
4 | extern AXError _AXUIElementGetWindow(AXUIElementRef ref, uint32_t *wid);
5 |
6 | static const char *bool_str[] = { "off", "on" };
7 |
8 | struct signal_args
9 | {
10 | char name[2][255];
11 | char value[2][255];
12 | void *entity;
13 | void *param1;
14 | };
15 |
16 | struct rgba_color
17 | {
18 | bool is_valid;
19 | uint32_t p;
20 | float r;
21 | float g;
22 | float b;
23 | float a;
24 | };
25 |
26 | static struct rgba_color
27 | rgba_color_from_hex(uint32_t color)
28 | {
29 | struct rgba_color result;
30 | result.is_valid = true;
31 | result.p = color;
32 | result.r = ((color >> 16) & 0xff) / 255.0;
33 | result.g = ((color >> 8) & 0xff) / 255.0;
34 | result.b = ((color >> 0) & 0xff) / 255.0;
35 | result.a = ((color >> 24) & 0xff) / 255.0;
36 | return result;
37 | }
38 |
39 | static inline bool is_root(void)
40 | {
41 | return getuid() == 0 || geteuid() == 0;
42 | }
43 |
44 | static inline bool string_equals(const char *a, const char *b)
45 | {
46 | return a && b && strcmp(a, b) == 0;
47 | }
48 |
49 | static inline char *string_escape_quote(char *s)
50 | {
51 | if (!s) return NULL;
52 |
53 | char *cursor = s;
54 | int num_quotes = 0;
55 |
56 | while (*cursor) {
57 | if (*cursor == '"') ++num_quotes;
58 | ++cursor;
59 | }
60 |
61 | if (!num_quotes) return NULL;
62 |
63 | int size_in_bytes = (int)(cursor - s) + num_quotes;
64 | char *result = malloc(sizeof(char) * (size_in_bytes+1));
65 | result[size_in_bytes] = '\0';
66 |
67 | for (char *dst = result, *cursor = s; *cursor; ++cursor) {
68 | if (*cursor == '"') *dst++ = '\\';
69 | *dst++ = *cursor;
70 | }
71 |
72 | return result;
73 | }
74 |
75 | static inline char *cfstring_copy(CFStringRef string)
76 | {
77 | CFIndex num_bytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);
78 | char *result = malloc(num_bytes + 1);
79 | if (!result) return NULL;
80 |
81 | if (!CFStringGetCString(string, result, num_bytes + 1, kCFStringEncodingUTF8)) {
82 | free(result);
83 | result = NULL;
84 | }
85 |
86 | return result;
87 | }
88 |
89 | static inline char *string_copy(char *s)
90 | {
91 | int length = strlen(s);
92 | char *result = malloc(length + 1);
93 | if (!result) return NULL;
94 |
95 | memcpy(result, s, length);
96 | result[length] = '\0';
97 | return result;
98 | }
99 |
100 | static inline bool file_exists(char *filename)
101 | {
102 | struct stat buffer;
103 |
104 | if (stat(filename, &buffer) != 0) {
105 | return false;
106 | }
107 |
108 | if (buffer.st_mode & S_IFDIR) {
109 | return false;
110 | }
111 |
112 | return true;
113 | }
114 |
115 | static inline bool ensure_executable_permission(char *filename)
116 | {
117 | struct stat buffer;
118 |
119 | if (stat(filename, &buffer) != 0) {
120 | return false;
121 | }
122 |
123 | bool is_executable = buffer.st_mode & S_IXUSR;
124 | if (!is_executable && chmod(filename, S_IXUSR | buffer.st_mode) != 0) {
125 | return false;
126 | }
127 |
128 | return true;
129 | }
130 |
131 | static bool fork_exec(char *command, struct signal_args *args)
132 | {
133 | int pid = fork();
134 | if (pid == -1) return false;
135 | if (pid != 0) return true;
136 |
137 | if (args) {
138 | if (*args->name[0]) setenv(args->name[0], args->value[0], 1);
139 | if (*args->name[1]) setenv(args->name[1], args->value[1], 1);
140 | }
141 |
142 | char *exec[] = { "/usr/bin/env", "sh", "-c", command, NULL};
143 | exit(execvp(exec[0], exec));
144 | }
145 |
146 | static bool ax_privilege(void)
147 | {
148 | const void *keys[] = { kAXTrustedCheckOptionPrompt };
149 | const void *values[] = { kCFBooleanTrue };
150 | CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, array_count(keys), &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
151 | bool result = AXIsProcessTrustedWithOptions(options);
152 | CFRelease(options);
153 | return result;
154 | }
155 |
156 | static inline uint32_t ax_window_id(AXUIElementRef ref)
157 | {
158 | uint32_t wid = 0;
159 | _AXUIElementGetWindow(ref, &wid);
160 | return wid;
161 | }
162 |
163 | static inline pid_t ax_window_pid(AXUIElementRef ref)
164 | {
165 | return *(pid_t *)((void *) ref + 0x10);
166 | }
167 |
168 | #pragma clang diagnostic push
169 | #pragma clang diagnostic ignored "-Wdeprecated-declarations"
170 | static inline bool psn_equals(ProcessSerialNumber *a, ProcessSerialNumber *b)
171 | {
172 | Boolean result;
173 | SameProcess(a, b, &result);
174 | return result == 1;
175 | }
176 | #pragma clang diagnostic pop
177 |
178 | static inline float clampf_range(float value, float min, float max)
179 | {
180 | if (value < min) return min;
181 | if (value > max) return max;
182 | return value;
183 | }
184 |
185 | #endif
186 |
--------------------------------------------------------------------------------
/src/misc/log.h:
--------------------------------------------------------------------------------
1 | #ifndef LOG_H
2 | #define LOG_H
3 |
4 | extern bool g_verbose;
5 |
6 | static inline void
7 | debug(const char *format, ...)
8 | {
9 | if (!g_verbose) return;
10 |
11 | va_list args;
12 | va_start(args, format);
13 | vfprintf(stdout, format, args);
14 | va_end(args);
15 | }
16 |
17 | static inline void
18 | warn(const char *format, ...)
19 | {
20 | va_list args;
21 | va_start(args, format);
22 | vfprintf(stderr, format, args);
23 | va_end(args);
24 | }
25 |
26 | static inline void
27 | error(const char *format, ...)
28 | {
29 | va_list args;
30 | va_start(args, format);
31 | vfprintf(stderr, format, args);
32 | va_end(args);
33 | exit(EXIT_FAILURE);
34 | }
35 |
36 | #endif
37 |
--------------------------------------------------------------------------------
/src/misc/macros.h:
--------------------------------------------------------------------------------
1 | #ifndef MACROS_H
2 | #define MACROS_H
3 |
4 | #define array_count(a) (sizeof((a)) / sizeof(*(a)))
5 | #define min(a, b) ((a) < (b) ? (a) : (b))
6 | #define max(a, b) ((a) > (b) ? (a) : (b))
7 | #define add_and_clamp_to_zero(a, b) (((a) + (b) <= 0) ? 0 : (a) + (b))
8 |
9 | #define MAXLEN 512
10 |
11 | #endif
12 |
--------------------------------------------------------------------------------
/src/misc/memory_pool.h:
--------------------------------------------------------------------------------
1 | #ifndef MEMORY_POOL_H
2 | #define MEMORY_POOL_H
3 |
4 | #define KILOBYTES(value) ((value) * 1024ULL)
5 | #define MEGABYTES(value) (KILOBYTES(value) * 1024ULL)
6 | #define GIGABYTES(value) (MEGABYTES(value) * 1024ULL)
7 |
8 | struct memory_pool
9 | {
10 | void *memory;
11 | uint64_t size;
12 | volatile uint64_t used;
13 | };
14 |
15 | bool memory_pool_init(struct memory_pool *pool, uint64_t size)
16 | {
17 | pool->used = 0;
18 | pool->size = size;
19 | pool->memory = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
20 | return pool->memory != NULL;
21 | }
22 |
23 | #define memory_pool_push(p, t) memory_pool_push_size(p, sizeof(t))
24 | void *memory_pool_push_size(struct memory_pool *pool, uint64_t size)
25 | {
26 | for (;;) {
27 | uint64_t used = pool->used;
28 | uint64_t new_used = used + size;
29 |
30 | if (new_used < pool->size) {
31 | if (__sync_bool_compare_and_swap(&pool->used, used, new_used)) {
32 | return pool->memory + used;
33 | }
34 | } else {
35 | if (__sync_bool_compare_and_swap(&pool->used, used, size)) {
36 | return pool->memory;
37 | }
38 | }
39 | }
40 | }
41 |
42 | #endif
43 |
--------------------------------------------------------------------------------
/src/misc/notify.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | static bool g_notify_init;
5 | static NSImage *g_notify_img;
6 |
7 | @implementation NSBundle(swizzle)
8 | - (NSString *)fake_bundleIdentifier
9 | {
10 | if (self == [NSBundle mainBundle]) {
11 | return @"ae.cmacr.spacebar";
12 | } else {
13 | return [self fake_bundleIdentifier];
14 | }
15 | }
16 | @end
17 |
18 | static bool notify_init(void)
19 | {
20 | Class c = objc_getClass("NSBundle");
21 | if (!c) return false;
22 |
23 | method_exchangeImplementations(class_getInstanceMethod(c, @selector(bundleIdentifier)), class_getInstanceMethod(c, @selector(fake_bundleIdentifier)));
24 | g_notify_img = [[[NSWorkspace sharedWorkspace] iconForFile:[[[NSBundle mainBundle] executablePath] stringByResolvingSymlinksInPath]] retain];
25 | g_notify_init = true;
26 |
27 | return true;
28 | }
29 |
30 | static void notify(const char *subtitle, const char *format, ...)
31 | {
32 | @autoreleasepool {
33 | if (!g_notify_init) notify_init();
34 |
35 | va_list args;
36 | va_start(args, format);
37 | NSUserNotification *notification = [[NSUserNotification alloc] init];
38 | notification.title = @"spacebar";
39 | notification.subtitle = [NSString stringWithUTF8String:subtitle];
40 | notification.informativeText = [[[NSString alloc] initWithFormat:[NSString stringWithUTF8String:format] arguments:args] autorelease];
41 | [notification setValue:g_notify_img forKey:@"_identityImage"];
42 | [notification setValue:@(false) forKey:@"_identityImageHasBorder"];
43 | [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
44 | [notification release];
45 | va_end(args);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/misc/sbuffer.h:
--------------------------------------------------------------------------------
1 | #ifndef SBUFFER_H
2 | #define SBUFFER_H
3 |
4 | struct buf_hdr
5 | {
6 | size_t len;
7 | size_t cap;
8 | char buf[0];
9 | };
10 |
11 | #define OFFSETOF(t, f) (size_t)((char *)&(((t *)0)->f) - (char *)0)
12 |
13 | #define buf__hdr(b) ((struct buf_hdr *)((char *)(b) - OFFSETOF(struct buf_hdr, buf)))
14 | #define buf__should_grow(b, n) (buf_len(b) + (n) >= buf_cap(b))
15 | #define buf__fit(b, n) (buf__should_grow(b, n) ? ((b) = buf__grow_f(b, buf_len(b) + (n), sizeof(*(b)))) : 0)
16 |
17 | #define buf_len(b) ((b) ? buf__hdr(b)->len : 0)
18 | #define buf_cap(b) ((b) ? buf__hdr(b)->cap : 0)
19 | #define buf_last(b) ((b)[buf_len(b)-1])
20 | #define buf_push(b, x) (buf__fit(b, 1), (b)[buf_len(b)] = (x), buf__hdr(b)->len++)
21 | #define buf_del(b, x) ((b) ? (b)[x] = (b)[buf_len(b)-1], buf__hdr(b)->len-- : 0)
22 | #define buf_free(b) ((b) ? free(buf__hdr(b)) : 0)
23 |
24 | static void *buf__grow_f(const void *buf, size_t new_len, size_t elem_size)
25 | {
26 | size_t new_cap = max(1 + 2*buf_cap(buf), new_len);
27 | size_t new_size = OFFSETOF(struct buf_hdr, buf) + new_cap*elem_size;
28 | struct buf_hdr *new_hdr = realloc(buf ? buf__hdr(buf) : 0, new_size);
29 | new_hdr->cap = new_cap;
30 | if (!buf) {
31 | new_hdr->len = 0;
32 | }
33 | return new_hdr->buf;
34 | }
35 |
36 | #endif
37 |
--------------------------------------------------------------------------------
/src/misc/socket.c:
--------------------------------------------------------------------------------
1 | #include "socket.h"
2 |
3 | char *socket_read(int sockfd, int *len)
4 | {
5 | int cursor = 0;
6 | int bytes_read = 0;
7 | char *result = NULL;
8 | char buffer[BUFSIZ];
9 |
10 | while ((bytes_read = read(sockfd, buffer, sizeof(buffer)-1)) > 0) {
11 | char *temp = realloc(result, cursor+bytes_read+1);
12 | if (!temp) goto err;
13 |
14 | result = temp;
15 | memcpy(result+cursor, buffer, bytes_read);
16 | cursor += bytes_read;
17 |
18 | if (((result+cursor)[-1] == '\0') &&
19 | ((result+cursor)[-2] == '\0')) {
20 |
21 | // NOTE(cmacrae): if our message ends with double null-terminator we
22 | // have successfully received the entire message. this was added because
23 | // on macOS Big Sur we would in a few rare cases read the message AND YET
24 | // still enter another call to *read* above that would block, because the
25 | // client was finished sending its message and is blocking in a poll loop
26 | // waiting for a response.
27 |
28 | break;
29 | }
30 | }
31 |
32 | if (result && bytes_read != -1) {
33 | result[cursor] = '\0';
34 | *len = cursor;
35 | } else {
36 | err:
37 | if (result) free(result);
38 | result = NULL;
39 | *len = 0;
40 | }
41 |
42 | return result;
43 | }
44 |
45 | bool socket_write_bytes(int sockfd, char *message, int len)
46 | {
47 | return send(sockfd, message, len, 0) != -1;
48 | }
49 |
50 | bool socket_write(int sockfd, char *message)
51 | {
52 | return send(sockfd, message, strlen(message), 0) != -1;
53 | }
54 |
55 | bool socket_connect_in(int *sockfd, int port)
56 | {
57 | struct sockaddr_in socket_address;
58 |
59 | *sockfd = socket(PF_INET, SOCK_STREAM, 0);
60 | if (*sockfd == -1) return false;
61 |
62 | socket_address.sin_family = AF_INET;
63 | socket_address.sin_port = htons(port);
64 | socket_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
65 | memset(&socket_address.sin_zero, '\0', 8);
66 |
67 | return connect(*sockfd, (struct sockaddr*) &socket_address, sizeof(struct sockaddr)) != -1;
68 | }
69 |
70 | bool socket_connect_un(int *sockfd, char *socket_path)
71 | {
72 | struct sockaddr_un socket_address;
73 | socket_address.sun_family = AF_UNIX;
74 |
75 | *sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
76 | if (*sockfd == -1) return false;
77 |
78 | snprintf(socket_address.sun_path, sizeof(socket_address.sun_path), "%s", socket_path);
79 | return connect(*sockfd, (struct sockaddr *) &socket_address, sizeof(socket_address)) != -1;
80 | }
81 |
82 | void socket_wait(int sockfd)
83 | {
84 | struct pollfd fds[] = {
85 | { sockfd, POLLIN, 0 }
86 | };
87 |
88 | char dummy[1];
89 | int bytes = 0;
90 |
91 | while (poll(fds, 1, -1) > 0) {
92 | if (fds[0].revents & POLLIN) {
93 | if ((bytes = recv(sockfd, dummy, 0, 0)) <= 0) {
94 | break;
95 | }
96 | }
97 | }
98 | }
99 |
100 | void socket_close(int sockfd)
101 | {
102 | shutdown(sockfd, SHUT_RDWR);
103 | close(sockfd);
104 | }
105 |
106 | static void *socket_connection_handler(void *context)
107 | {
108 | struct daemon *daemon = context;
109 |
110 | while (daemon->is_running) {
111 | int sockfd = accept(daemon->sockfd, NULL, 0);
112 | if (sockfd == -1) continue;
113 |
114 | int length;
115 | char *message = socket_read(sockfd, &length);
116 | if (message) {
117 | daemon->handler(message, length, sockfd);
118 | } else {
119 | socket_close(sockfd);
120 | }
121 | }
122 |
123 | return NULL;
124 | }
125 |
126 | bool socket_daemon_begin_in(struct daemon *daemon, int port, socket_daemon_handler *handler)
127 | {
128 | struct sockaddr_in socket_address;
129 | socket_address.sin_family = AF_INET;
130 | socket_address.sin_port = htons(port);
131 | socket_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
132 | memset(&socket_address.sin_zero, '\0', 8);
133 |
134 | if ((daemon->sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
135 | return false;
136 | }
137 |
138 | if (bind(daemon->sockfd, (struct sockaddr *) &socket_address, sizeof(socket_address)) == -1) {
139 | return false;
140 | }
141 |
142 | if (listen(daemon->sockfd, SOMAXCONN) == -1) {
143 | return false;
144 | }
145 |
146 | daemon->handler = handler;
147 | daemon->is_running = true;
148 | pthread_create(&daemon->thread, NULL, &socket_connection_handler, daemon);
149 |
150 | return true;
151 | }
152 |
153 | bool socket_daemon_begin_un(struct daemon *daemon, char *socket_path, socket_daemon_handler *handler)
154 | {
155 | struct sockaddr_un socket_address;
156 | socket_address.sun_family = AF_UNIX;
157 | snprintf(socket_address.sun_path, sizeof(socket_address.sun_path), "%s", socket_path);
158 | unlink(socket_path);
159 |
160 | if ((daemon->sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
161 | return false;
162 | }
163 |
164 | if (bind(daemon->sockfd, (struct sockaddr *) &socket_address, sizeof(socket_address)) == -1) {
165 | return false;
166 | }
167 |
168 | if (chmod(socket_path, 0600) != 0) {
169 | return false;
170 | }
171 |
172 | if (listen(daemon->sockfd, SOMAXCONN) == -1) {
173 | return false;
174 | }
175 |
176 | daemon->handler = handler;
177 | daemon->is_running = true;
178 | pthread_create(&daemon->thread, NULL, &socket_connection_handler, daemon);
179 |
180 | return true;
181 | }
182 |
183 | void socket_daemon_end(struct daemon *daemon)
184 | {
185 | daemon->is_running = false;
186 | pthread_join(daemon->thread, NULL);
187 | socket_close(daemon->sockfd);
188 | }
189 |
--------------------------------------------------------------------------------
/src/misc/socket.h:
--------------------------------------------------------------------------------
1 | #ifndef SOCKET_H
2 | #define SOCKET_H
3 |
4 | #define SOCKET_DAEMON_HANDLER(name) void name(char *message, int length, int sockfd)
5 | typedef SOCKET_DAEMON_HANDLER(socket_daemon_handler);
6 |
7 | #define FAILURE_MESSAGE "\x07"
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | struct daemon
21 | {
22 | int sockfd;
23 | bool is_running;
24 | pthread_t thread;
25 | socket_daemon_handler *handler;
26 | };
27 |
28 | char *socket_read(int sockfd, int *len);
29 | bool socket_write_bytes(int sockfd, char *message, int len);
30 | bool socket_write(int sockfd, char *message);
31 | bool socket_connect_in(int *sockfd, int port);
32 | bool socket_connect_un(int *sockfd, char *socket_path);
33 | void socket_wait(int sockfd);
34 | void socket_close(int sockfd);
35 | bool socket_daemon_begin_in(struct daemon *daemon, int port, socket_daemon_handler *handler);
36 | bool socket_daemon_begin_un(struct daemon *daemon, char *socket_path, socket_daemon_handler *handler);
37 | void socket_daemon_end(struct daemon *daemon);
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/src/misc/timing.h:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | static inline uint64_t time_clock(void)
5 | {
6 | return mach_absolute_time();
7 | }
8 |
9 | #pragma clang diagnostic push
10 | #pragma clang diagnostic ignored "-Wdeprecated-declarations"
11 | static inline uint64_t time_elapsed_ns(uint64_t begin, uint64_t end)
12 | {
13 | uint64_t elapsed = end - begin;
14 | Nanoseconds nano = AbsoluteToNanoseconds(*(AbsoluteTime *) &elapsed);
15 | return *(uint64_t *) &nano;
16 | }
17 | #pragma clang diagnostic pop
18 |
19 | static inline double time_elapsed_ms(uint64_t begin, uint64_t end)
20 | {
21 | uint64_t ns = time_elapsed_ns(begin, end);
22 | return (double)(ns / 1000000.0);
23 | }
24 |
25 | static inline double time_elapsed_s(uint64_t begin, uint64_t end)
26 | {
27 | uint64_t ns = time_elapsed_ns(begin, end);
28 | return (double)(ns / 1000000000.0);
29 | }
30 |
--------------------------------------------------------------------------------
/src/process_manager.c:
--------------------------------------------------------------------------------
1 | #include "process_manager.h"
2 |
3 | extern struct event_loop g_event_loop;
4 |
5 | static TABLE_HASH_FUNC(hash_psn)
6 | {
7 | unsigned long result = ((ProcessSerialNumber*) key)->lowLongOfPSN;
8 | result = (result + 0x7ed55d16) + (result << 12);
9 | result = (result ^ 0xc761c23c) ^ (result >> 19);
10 | result = (result + 0x165667b1) + (result << 5);
11 | result = (result + 0xd3a2646c) ^ (result << 9);
12 | result = (result + 0xfd7046c5) + (result << 3);
13 | result = (result ^ 0xb55a4f09) ^ (result >> 16);
14 | return result;
15 | }
16 |
17 | static TABLE_COMPARE_FUNC(compare_psn)
18 | {
19 | return psn_equals(key_a, key_b);
20 | }
21 |
22 | #pragma clang diagnostic push
23 | #pragma clang diagnostic ignored "-Wdeprecated-declarations"
24 | struct process *process_create(ProcessSerialNumber psn)
25 | {
26 | struct process *process = malloc(sizeof(struct process));
27 | memset(process, 0, sizeof(struct process));
28 |
29 | CFStringRef process_name_ref;
30 | if (CopyProcessName(&psn, &process_name_ref) == noErr) {
31 | process->name = cfstring_copy(process_name_ref);
32 | CFRelease(process_name_ref);
33 | } else {
34 | process->name = string_copy("");
35 | }
36 |
37 | ProcessInfoRec process_info = {};
38 | process_info.processInfoLength = sizeof(ProcessInfoRec);
39 | GetProcessInformation(&psn, &process_info);
40 |
41 | process->psn = psn;
42 | GetProcessPID(&process->psn, &process->pid);
43 | process->background = (process_info.processMode & modeOnlyBackground) != 0;
44 | process->xpc = process_info.processType == 'XPC!';
45 |
46 | CFDictionaryRef process_dict = ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
47 | if (process_dict) {
48 | CFBooleanRef process_lsuielement = CFDictionaryGetValue(process_dict, CFSTR("LSUIElement"));
49 | if (process_lsuielement) process->lsuielement = CFBooleanGetValue(process_lsuielement);
50 | CFBooleanRef process_lsbackground = CFDictionaryGetValue(process_dict, CFSTR("LSBackgroundOnly"));
51 | if (process_lsbackground) process->lsbackground = CFBooleanGetValue(process_lsbackground);
52 | CFRelease(process_dict);
53 | }
54 |
55 | return process;
56 | }
57 |
58 | void process_destroy(struct process *process)
59 | {
60 | free(process->name);
61 | free(process);
62 | }
63 |
64 | static bool process_is_observable(struct process *process)
65 | {
66 | if (process->lsbackground) {
67 | debug("%s: %s was marked as background only! ignoring..\n", __FUNCTION__, process->name);
68 | return false;
69 | }
70 |
71 | if (process->lsuielement) {
72 | debug("%s: %s was marked as agent! ignoring..\n", __FUNCTION__, process->name);
73 | return false;
74 | }
75 |
76 | if (process->background) {
77 | debug("%s: %s was marked as daemon! ignoring..\n", __FUNCTION__, process->name);
78 | return false;
79 | }
80 |
81 | if (process->xpc) {
82 | debug("%s: %s was marked as xpc service! ignoring..\n", __FUNCTION__, process->name);
83 | return false;
84 | }
85 |
86 | return true;
87 | }
88 |
89 | static PROCESS_EVENT_HANDLER(process_handler)
90 | {
91 | struct process_manager *pm = (struct process_manager *) user_data;
92 |
93 | ProcessSerialNumber psn;
94 | if (GetEventParameter(event, kEventParamProcessID, typeProcessSerialNumber, NULL, sizeof(psn), NULL, &psn) != noErr) {
95 | return -1;
96 | }
97 |
98 | switch (GetEventKind(event)) {
99 | case kEventAppLaunched: {
100 | struct process *process = process_create(psn);
101 | if (!process) return noErr;
102 |
103 | if (process_is_observable(process)) {
104 | struct event *event = event_create(&g_event_loop, APPLICATION_LAUNCHED, process);
105 | event_loop_post(&g_event_loop, event);
106 | process_manager_add_process(pm, process);
107 | } else {
108 | process_destroy(process);
109 | }
110 | } break;
111 | case kEventAppTerminated: {
112 | struct process *process = process_manager_find_process(pm, &psn);
113 | if (!process) return noErr;
114 |
115 | process->terminated = true;
116 | process_manager_remove_process(pm, &psn);
117 |
118 | struct event *event = event_create(&g_event_loop, APPLICATION_TERMINATED, process);
119 | event_loop_post(&g_event_loop, event);
120 | } break;
121 | case kEventAppFrontSwitched: {
122 | struct process *process = process_manager_find_process(pm, &psn);
123 | if (!process) return noErr;
124 |
125 | struct event *event = event_create(&g_event_loop, APPLICATION_FRONT_SWITCHED, process);
126 | event_loop_post(&g_event_loop, event);
127 | } break;
128 | }
129 |
130 | return noErr;
131 | }
132 |
133 | static void
134 | process_manager_add_running_processes(struct process_manager *pm)
135 | {
136 | ProcessSerialNumber psn = { kNoProcess, kNoProcess };
137 | while (GetNextProcess(&psn) == noErr) {
138 | struct process *process = process_create(psn);
139 | if (!process) continue;
140 |
141 | if (process_is_observable(process)) {
142 | if (string_equals(process->name, "Finder")) {
143 | debug("%s: %s was found! caching psn..\n", __FUNCTION__, process->name);
144 | pm->finder_psn = psn;
145 | }
146 |
147 | process_manager_add_process(pm, process);
148 | } else {
149 | process_destroy(process);
150 | }
151 | }
152 | }
153 | #pragma clang diagnostic pop
154 |
155 | struct process *process_manager_find_process(struct process_manager *pm, ProcessSerialNumber *psn)
156 | {
157 | return table_find(&pm->process, psn);
158 | }
159 |
160 | void process_manager_remove_process(struct process_manager *pm, ProcessSerialNumber *psn)
161 | {
162 | table_remove(&pm->process, psn);
163 | }
164 |
165 | void process_manager_add_process(struct process_manager *pm, struct process *process)
166 | {
167 | table_add(&pm->process, &process->psn, process);
168 | }
169 |
170 | #if 0
171 | bool process_manager_next_process(ProcessSerialNumber *next_psn)
172 | {
173 | CFArrayRef applications =_LSCopyApplicationArrayInFrontToBackOrder(0xFFFFFFFE, 1);
174 | if (!applications) return false;
175 |
176 | bool found_front_psn = false;
177 | ProcessSerialNumber front_psn;
178 | _SLPSGetFrontProcess(&front_psn);
179 |
180 | for (int i = 0; i < CFArrayGetCount(applications); ++i) {
181 | CFTypeRef asn = CFArrayGetValueAtIndex(applications, i);
182 | assert(CFGetTypeID(asn) == _LSASNGetTypeID());
183 | _LSASNExtractHighAndLowParts(asn, &next_psn->highLongOfPSN, &next_psn->lowLongOfPSN);
184 | if (found_front_psn) break;
185 | found_front_psn = psn_equals(&front_psn, next_psn);
186 | }
187 |
188 | CFRelease(applications);
189 | return true;
190 | }
191 | #endif
192 |
193 | void process_manager_init(struct process_manager *pm)
194 | {
195 | pm->target = GetApplicationEventTarget();
196 | pm->handler = NewEventHandlerUPP(process_handler);
197 | pm->type[0].eventClass = kEventClassApplication;
198 | pm->type[0].eventKind = kEventAppLaunched;
199 | pm->type[1].eventClass = kEventClassApplication;
200 | pm->type[1].eventKind = kEventAppTerminated;
201 | pm->type[2].eventClass = kEventClassApplication;
202 | pm->type[2].eventKind = kEventAppFrontSwitched;
203 | table_init(&pm->process, 125, hash_psn, compare_psn);
204 | process_manager_add_running_processes(pm);
205 | }
206 |
207 | #pragma clang diagnostic push
208 | #pragma clang diagnostic ignored "-Wdeprecated-declarations"
209 | bool process_manager_begin(struct process_manager *pm)
210 | {
211 | ProcessSerialNumber front_psn;
212 | _SLPSGetFrontProcess(&front_psn);
213 | GetProcessPID(&front_psn, &g_process_manager.front_pid);
214 | g_process_manager.last_front_pid = g_process_manager.front_pid;
215 | return InstallEventHandler(pm->target, pm->handler, 3, pm->type, pm, &pm->ref) == noErr;
216 | }
217 | #pragma clang diagnostic pop
218 |
219 | bool process_manager_end(struct process_manager *pm)
220 | {
221 | return RemoveEventHandler(pm->ref) == noErr;
222 | }
223 |
--------------------------------------------------------------------------------
/src/process_manager.h:
--------------------------------------------------------------------------------
1 | #ifndef PROCESS_MANAGER_H
2 | #define PROCESS_MANAGER_H
3 |
4 | extern OSStatus _SLPSGetFrontProcess(ProcessSerialNumber *psn);
5 | extern CFStringRef SLSCopyBestManagedDisplayForRect(int cid, CGRect rect);
6 | extern CGError SLSGetCurrentCursorLocation(int cid, CGPoint *point);
7 |
8 | #if 0
9 | extern CFArrayRef _LSCopyApplicationArrayInFrontToBackOrder(int negative_one, int one);
10 | extern void _LSASNExtractHighAndLowParts(const void *asn, uint32_t *high, uint32_t *low);
11 | extern CFTypeID _LSASNGetTypeID(void);
12 | #endif
13 |
14 | #define PROCESS_EVENT_HANDLER(name) OSStatus name(EventHandlerCallRef ref, EventRef event, void *user_data)
15 | typedef PROCESS_EVENT_HANDLER(process_event_handler);
16 |
17 | struct process
18 | {
19 | ProcessSerialNumber psn;
20 | pid_t pid;
21 | char *name;
22 | bool background;
23 | bool lsuielement;
24 | bool lsbackground;
25 | bool xpc;
26 | bool volatile terminated;
27 | };
28 |
29 | struct process_manager
30 | {
31 | struct table process;
32 | EventTargetRef target;
33 | EventHandlerUPP handler;
34 | EventTypeSpec type[3];
35 | EventHandlerRef ref;
36 | pid_t front_pid;
37 | pid_t last_front_pid;
38 | ProcessSerialNumber finder_psn;
39 | };
40 |
41 | void process_destroy(struct process *process);
42 | struct process *process_create(ProcessSerialNumber psn);
43 | struct process *process_manager_find_process(struct process_manager *pm, ProcessSerialNumber *psn);
44 | void process_manager_remove_process(struct process_manager *pm, ProcessSerialNumber *psn);
45 | void process_manager_add_process(struct process_manager *pm, struct process *process);
46 | // bool process_manager_next_process(ProcessSerialNumber *next_psn);
47 | void process_manager_init(struct process_manager *pm);
48 | bool process_manager_begin(struct process_manager *pm);
49 | bool process_manager_end(struct process_manager *pm);
50 |
51 | #endif
52 |
--------------------------------------------------------------------------------
/src/spacebar.c:
--------------------------------------------------------------------------------
1 | #define SOCKET_PATH_FMT "/tmp/spacebar_%s.socket"
2 | #define LCFILE_PATH_FMT "/tmp/spacebar_%s.lock"
3 |
4 | #define CLIENT_OPT_LONG "--message"
5 | #define CLIENT_OPT_SHRT "-m"
6 |
7 | #define DEBUG_VERBOSE_OPT_LONG "--verbose"
8 | #define DEBUG_VERBOSE_OPT_SHRT "-V"
9 | #define VERSION_OPT_LONG "--version"
10 | #define VERSION_OPT_SHRT "-v"
11 | #define CONFIG_OPT_LONG "--config"
12 | #define CONFIG_OPT_SHRT "-c"
13 |
14 | #define MAJOR 1
15 | #define MINOR 4
16 | #define PATCH 0
17 |
18 | extern int SLSMainConnectionID(void);
19 |
20 | #define CONNECTION_CALLBACK(name) void name(uint32_t type, void *data, size_t data_length, void *context, int cid)
21 | typedef CONNECTION_CALLBACK(connection_callback);
22 | extern CGError SLSRegisterConnectionNotifyProc(int cid, connection_callback *handler, uint32_t event, void *context);
23 |
24 | struct event_loop g_event_loop;
25 | void *g_workspace_context;
26 | struct process_manager g_process_manager;
27 | struct display_manager g_display_manager;
28 | struct application_manager g_application_manager;
29 | struct daemon g_daemon;
30 | struct bar_manager g_bar_manager;
31 | int g_connection;
32 |
33 | char g_socket_file[MAXLEN];
34 | char g_config_file[4096];
35 | char g_lock_file[MAXLEN];
36 | bool g_verbose;
37 |
38 | static int client_send_message(int argc, char **argv)
39 | {
40 | if (argc <= 1) {
41 | error("spacebar-msg: no arguments given! abort..\n");
42 | }
43 |
44 | char *user = getenv("USER");
45 | if (!user) {
46 | error("spacebar-msg: 'env USER' not set! abort..\n");
47 | }
48 |
49 | int sockfd;
50 | char socket_file[MAXLEN];
51 | snprintf(socket_file, sizeof(socket_file), SOCKET_PATH_FMT, user);
52 |
53 | if (!socket_connect_un(&sockfd, socket_file)) {
54 | error("spacebar-msg: failed to connect to socket..\n");
55 | }
56 |
57 | int message_length = argc;
58 | int argl[argc];
59 |
60 | for (int i = 1; i < argc; ++i) {
61 | argl[i] = strlen(argv[i]);
62 | message_length += argl[i];
63 | }
64 |
65 | char message[message_length];
66 | char *temp = message;
67 |
68 | for (int i = 1; i < argc; ++i) {
69 | memcpy(temp, argv[i], argl[i]);
70 | temp += argl[i];
71 | *temp++ = '\0';
72 | }
73 | *temp++ = '\0';
74 |
75 | if (!socket_write_bytes(sockfd, message, message_length)) {
76 | error("spacebar-msg: failed to send data..\n");
77 | }
78 |
79 | shutdown(sockfd, SHUT_WR);
80 |
81 | int result = EXIT_SUCCESS;
82 | int byte_count = 0;
83 | char rsp[BUFSIZ];
84 |
85 | struct pollfd fds[] = {
86 | { sockfd, POLLIN, 0 }
87 | };
88 |
89 | while (poll(fds, 1, -1) > 0) {
90 | if (fds[0].revents & POLLIN) {
91 | if ((byte_count = recv(sockfd, rsp, sizeof(rsp)-1, 0)) <= 0) {
92 | break;
93 | }
94 |
95 | rsp[byte_count] = '\0';
96 |
97 | if (rsp[0] == FAILURE_MESSAGE[0]) {
98 | result = EXIT_FAILURE;
99 | fprintf(stderr, "%s", rsp + 1);
100 | fflush(stderr);
101 | } else {
102 | fprintf(stdout, "%s", rsp);
103 | fflush(stdout);
104 | }
105 | }
106 | }
107 |
108 | socket_close(sockfd);
109 | return result;
110 | }
111 |
112 | static void acquire_lockfile(void)
113 | {
114 | int handle = open(g_lock_file, O_CREAT | O_WRONLY, 0600);
115 | if (handle == -1) {
116 | error("spacebar: could not create lock-file! abort..\n");
117 | }
118 |
119 | struct flock lockfd = {
120 | .l_start = 0,
121 | .l_len = 0,
122 | .l_pid = getpid(),
123 | .l_type = F_WRLCK,
124 | .l_whence = SEEK_SET
125 | };
126 |
127 | if (fcntl(handle, F_SETLK, &lockfd) == -1) {
128 | error("spacebar: could not acquire lock-file! abort..\n");
129 | }
130 | }
131 |
132 | static bool get_config_file(char *restrict filename, char *restrict buffer, int buffer_size)
133 | {
134 | char *xdg_home = getenv("XDG_CONFIG_HOME");
135 | if (xdg_home && *xdg_home) {
136 | snprintf(buffer, buffer_size, "%s/spacebar/%s", xdg_home, filename);
137 | if (file_exists(buffer)) return true;
138 | }
139 |
140 | char *home = getenv("HOME");
141 | if (!home) return false;
142 |
143 | snprintf(buffer, buffer_size, "%s/.config/spacebar/%s", home, filename);
144 | if (file_exists(buffer)) return true;
145 |
146 | snprintf(buffer, buffer_size, "%s/.%s", home, filename);
147 | return file_exists(buffer);
148 | }
149 |
150 | static void exec_config_file(void)
151 | {
152 | if (!*g_config_file && !get_config_file("spacebarrc", g_config_file, sizeof(g_config_file))) {
153 | notify("configuration", "could not locate config file..");
154 | return;
155 | }
156 |
157 | if (!file_exists(g_config_file)) {
158 | notify("configuration", "file '%s' does not exist..", g_config_file);
159 | return;
160 | }
161 |
162 | if (!ensure_executable_permission(g_config_file)) {
163 | notify("configuration", "could not set the executable permission bit for '%s'", g_config_file);
164 | return;
165 | }
166 |
167 | if (!fork_exec(g_config_file, NULL)) {
168 | notify("configuration", "failed to execute file '%s'", g_config_file);
169 | return;
170 | }
171 | }
172 |
173 | #pragma clang diagnostic push
174 | #pragma clang diagnostic ignored "-Wdeprecated-declarations"
175 | static inline void init_misc_settings(void)
176 | {
177 | char *user = getenv("USER");
178 | if (!user) {
179 | error("spacebar: 'env USER' not set! abort..\n");
180 | }
181 |
182 | snprintf(g_socket_file, sizeof(g_socket_file), SOCKET_PATH_FMT, user);
183 | snprintf(g_lock_file, sizeof(g_lock_file), LCFILE_PATH_FMT, user);
184 |
185 | NSApplicationLoad();
186 | signal(SIGCHLD, SIG_IGN);
187 | signal(SIGPIPE, SIG_IGN);
188 | CGSetLocalEventsSuppressionInterval(0.0f);
189 | CGEnableEventStateCombining(false);
190 | g_connection = SLSMainConnectionID();
191 | }
192 | #pragma clang diagnostic pop
193 |
194 | static CONNECTION_CALLBACK(connection_handler)
195 | {
196 | }
197 |
198 | static void parse_arguments(int argc, char **argv)
199 | {
200 | if ((string_equals(argv[1], VERSION_OPT_LONG)) ||
201 | (string_equals(argv[1], VERSION_OPT_SHRT))) {
202 | fprintf(stdout, "spacebar-v%d.%d.%d\n", MAJOR, MINOR, PATCH);
203 | exit(EXIT_SUCCESS);
204 | }
205 |
206 | if ((string_equals(argv[1], CLIENT_OPT_LONG)) ||
207 | (string_equals(argv[1], CLIENT_OPT_SHRT))) {
208 | exit(client_send_message(argc-1, argv+1));
209 | }
210 |
211 | for (int i = 1; i < argc; ++i) {
212 | char *opt = argv[i];
213 |
214 | if ((string_equals(opt, DEBUG_VERBOSE_OPT_LONG)) ||
215 | (string_equals(opt, DEBUG_VERBOSE_OPT_SHRT))) {
216 | g_verbose = true;
217 | } else if ((string_equals(opt, CONFIG_OPT_LONG)) ||
218 | (string_equals(opt, CONFIG_OPT_SHRT))) {
219 | char *val = i < argc - 1 ? argv[++i] : NULL;
220 | if (!val) error("spacebar: option '%s|%s' requires an argument!\n", CONFIG_OPT_LONG, CONFIG_OPT_SHRT);
221 | snprintf(g_config_file, sizeof(g_config_file), "%s", val);
222 | } else {
223 | error("spacebar: '%s' is not a valid option!\n", opt);
224 | }
225 | }
226 | }
227 |
228 | int main(int argc, char **argv)
229 | {
230 | if (argc > 1) {
231 | parse_arguments(argc, argv);
232 | }
233 |
234 | if (is_root()) {
235 | error("spacebar: running as root is not allowed! abort..\n");
236 | }
237 |
238 | if (!ax_privilege()) {
239 | error("spacebar: could not access accessibility features! abort..\n");
240 | }
241 |
242 | init_misc_settings();
243 | acquire_lockfile();
244 |
245 | if (!event_loop_init(&g_event_loop)) {
246 | error("spacebar: could not initialize event_loop! abort..\n");
247 | }
248 |
249 | process_manager_init(&g_process_manager);
250 | workspace_event_handler_init(&g_workspace_context);
251 | application_manager_init(&g_application_manager);
252 | bar_manager_init(&g_bar_manager);
253 |
254 | event_loop_begin(&g_event_loop);
255 | display_manager_begin(&g_display_manager);
256 | process_manager_begin(&g_process_manager);
257 | workspace_event_handler_begin(&g_workspace_context);
258 | application_manager_begin(&g_application_manager);
259 | bar_manager_begin(&g_bar_manager);
260 | SLSRegisterConnectionNotifyProc(g_connection, connection_handler, 1204, NULL);
261 |
262 | if (!socket_daemon_begin_un(&g_daemon, g_socket_file, message_handler)) {
263 | error("spacebar: could not initialize daemon! abort..\n");
264 | }
265 |
266 | exec_config_file();
267 | CFRunLoopRun();
268 | return 0;
269 | }
270 |
--------------------------------------------------------------------------------
/src/window.c:
--------------------------------------------------------------------------------
1 | #include "window.h"
2 |
3 | extern int g_connection;
4 | extern struct window_manager g_window_manager;
5 |
6 | int g_normal_window_level;
7 | int g_floating_window_level;
8 |
9 | static void
10 | window_observe_notification(struct window *window, int notification)
11 | {
12 | AXError result = AXObserverAddNotification(window->application->observer_ref, window->ref, ax_window_notification[notification], window->id_ptr);
13 | if (result == kAXErrorSuccess || result == kAXErrorNotificationAlreadyRegistered) window->notification |= 1 << notification;
14 | }
15 |
16 | static void
17 | window_unobserve_notification(struct window *window, int notification)
18 | {
19 | AXObserverRemoveNotification(window->application->observer_ref, window->ref, ax_window_notification[notification]);
20 | window->notification &= ~(1 << notification);
21 | }
22 |
23 | bool window_observe(struct window *window)
24 | {
25 | for (int i = 0; i < array_count(ax_window_notification); ++i) {
26 | window_observe_notification(window, i);
27 | }
28 |
29 | return (window->notification & AX_WINDOW_ALL) == AX_WINDOW_ALL;
30 | }
31 |
32 | void window_unobserve(struct window *window)
33 | {
34 | for (int i = 0; i < array_count(ax_window_notification); ++i) {
35 | if (!(window->notification & (1 << i))) continue;
36 | window_unobserve_notification(window, i);
37 | }
38 | }
39 |
40 | CFStringRef window_display_uuid(struct window *window)
41 | {
42 | CFStringRef uuid = SLSCopyManagedDisplayForWindow(g_connection, window->id);
43 | if (!uuid) {
44 | CGRect frame = window_frame(window);
45 | uuid = SLSCopyBestManagedDisplayForRect(g_connection, frame);
46 | }
47 | return uuid;
48 | }
49 |
50 | int window_display_id(struct window *window)
51 | {
52 | CFStringRef uuid_string = window_display_uuid(window);
53 | if (!uuid_string) return 0;
54 |
55 | CFUUIDRef uuid = CFUUIDCreateFromString(NULL, uuid_string);
56 | int id = CGDisplayGetDisplayIDFromUUID(uuid);
57 |
58 | CFRelease(uuid);
59 | CFRelease(uuid_string);
60 |
61 | return id;
62 | }
63 |
64 | uint64_t window_space(struct window *window)
65 | {
66 | uint64_t sid = 0;
67 | CFArrayRef window_list_ref = cfarray_of_cfnumbers(&window->id, sizeof(uint32_t), 1, kCFNumberSInt32Type);
68 | CFArrayRef space_list_ref = SLSCopySpacesForWindows(g_connection, 0x7, window_list_ref);
69 | if (!space_list_ref) goto err;
70 |
71 | int count = CFArrayGetCount(space_list_ref);
72 | if (count) {
73 | CFNumberRef id_ref = CFArrayGetValueAtIndex(space_list_ref, 0);
74 | CFNumberGetValue(id_ref, CFNumberGetType(id_ref), &sid);
75 | }
76 |
77 | CFRelease(space_list_ref);
78 | err:
79 | CFRelease(window_list_ref);
80 | return sid;
81 | }
82 |
83 | uint64_t *window_space_list(struct window *window, int *count)
84 | {
85 | uint64_t *space_list = NULL;
86 | CFArrayRef window_list_ref = cfarray_of_cfnumbers(&window->id, sizeof(uint32_t), 1, kCFNumberSInt32Type);
87 | CFArrayRef space_list_ref = SLSCopySpacesForWindows(g_connection, 0x7, window_list_ref);
88 | if (!space_list_ref) goto err;
89 |
90 | *count = CFArrayGetCount(space_list_ref);
91 | if (!*count) goto out;
92 |
93 | space_list = malloc(*count * sizeof(uint64_t));
94 | for (int i = 0; i < *count; ++i) {
95 | CFNumberRef id_ref = CFArrayGetValueAtIndex(space_list_ref, i);
96 | CFNumberGetValue(id_ref, CFNumberGetType(id_ref), space_list + i);
97 | }
98 |
99 | out:
100 | CFRelease(space_list_ref);
101 | err:
102 | CFRelease(window_list_ref);
103 | return space_list;
104 | }
105 |
106 | void window_serialize(FILE *rsp, struct window *window)
107 | {
108 | char *title = window_title(window);
109 | char *escaped_title = string_escape_quote(title);
110 | CGRect frame = window_frame(window);
111 | char *role = NULL;
112 | char *subrole = NULL;
113 | bool sticky = window_is_sticky(window);
114 | uint64_t sid = window_space(window);
115 | int space = space_manager_mission_control_index(sid);
116 | int display = display_arrangement(space_display_id(sid));
117 | bool visible = sticky || space_is_visible(sid);
118 | bool is_topmost = window_is_topmost(window);
119 |
120 | CFStringRef cfrole = window_role(window);
121 | if (cfrole) {
122 | role = cfstring_copy(cfrole);
123 | CFRelease(cfrole);
124 | }
125 |
126 | CFStringRef cfsubrole = window_subrole(window);
127 | if (cfsubrole) {
128 | subrole = cfstring_copy(cfsubrole);
129 | CFRelease(cfsubrole);
130 | }
131 |
132 | struct view *view = window_manager_find_managed_window(&g_window_manager, window);
133 | struct window_node *node = view ? view_find_window_node(view, window->id) : NULL;
134 |
135 | char split[MAXLEN];
136 | snprintf(split, sizeof(split), "%s", window_node_split_str[node && node->parent ? node->parent->split : 0]);
137 | bool zoom_parent = node && node->zoom && node->zoom == node->parent;
138 | bool zoom_fullscreen = node && node->zoom && node->zoom == view->root;
139 |
140 | fprintf(rsp,
141 | "{\n"
142 | "\t\"id\":%d,\n"
143 | "\t\"pid\":%d,\n"
144 | "\t\"app\":\"%s\",\n"
145 | "\t\"title\":\"%s\",\n"
146 | "\t\"frame\":{\n\t\t\"x\":%.4f,\n\t\t\"y\":%.4f,\n\t\t\"w\":%.4f,\n\t\t\"h\":%.4f\n\t},\n"
147 | "\t\"level\":%d,\n"
148 | "\t\"role\":\"%s\",\n"
149 | "\t\"subrole\":\"%s\",\n"
150 | "\t\"movable\":%d,\n"
151 | "\t\"resizable\":%d,\n"
152 | "\t\"display\":%d,\n"
153 | "\t\"space\":%d,\n"
154 | "\t\"visible\":%d,\n"
155 | "\t\"focused\":%d,\n"
156 | "\t\"split\":\"%s\",\n"
157 | "\t\"floating\":%d,\n"
158 | "\t\"sticky\":%d,\n"
159 | "\t\"topmost\":%d,\n"
160 | "\t\"border\":%d,\n"
161 | "\t\"shadow\":%d,\n"
162 | "\t\"zoom-parent\":%d,\n"
163 | "\t\"zoom-fullscreen\":%d,\n"
164 | "\t\"native-fullscreen\":%d\n"
165 | "}",
166 | window->id,
167 | window->application->pid,
168 | window->application->name,
169 | escaped_title ? escaped_title : title ? title : "",
170 | frame.origin.x, frame.origin.y,
171 | frame.size.width, frame.size.height,
172 | window_level(window),
173 | role ? role : "",
174 | subrole ? subrole : "",
175 | window_can_move(window),
176 | window_can_resize(window),
177 | display,
178 | space,
179 | visible,
180 | window->id == g_window_manager.focused_window_id,
181 | split,
182 | window->is_floating,
183 | sticky,
184 | is_topmost,
185 | window->border.enabled,
186 | window->has_shadow,
187 | zoom_parent,
188 | zoom_fullscreen,
189 | window_is_fullscreen(window));
190 |
191 | if (subrole) free(subrole);
192 | if (role) free(role);
193 | if (title) free(title);
194 | if (escaped_title) free(escaped_title);
195 | }
196 |
197 | char *window_title(struct window *window)
198 | {
199 | char *title = NULL;
200 | CFTypeRef value = NULL;
201 |
202 | #if 0
203 | SLSCopyWindowProperty(g_connection, window->id, CFSTR("kCGSWindowTitle"), &value);
204 | #else
205 | AXUIElementCopyAttributeValue(window->ref, kAXTitleAttribute, &value);
206 | #endif
207 |
208 | if (value) {
209 | title = cfstring_copy(value);
210 | CFRelease(value);
211 | }
212 |
213 | return title;
214 | }
215 |
216 | CGRect window_ax_frame(struct window *window)
217 | {
218 | CGRect frame = {};
219 | CFTypeRef position_ref = NULL;
220 | CFTypeRef size_ref = NULL;
221 |
222 | AXUIElementCopyAttributeValue(window->ref, kAXPositionAttribute, &position_ref);
223 | AXUIElementCopyAttributeValue(window->ref, kAXSizeAttribute, &size_ref);
224 |
225 | if (position_ref != NULL) {
226 | AXValueGetValue(position_ref, kAXValueTypeCGPoint, &frame.origin);
227 | CFRelease(position_ref);
228 | }
229 |
230 | if (size_ref != NULL) {
231 | AXValueGetValue(size_ref, kAXValueTypeCGSize, &frame.size);
232 | CFRelease(size_ref);
233 | }
234 |
235 | return frame;
236 | }
237 |
238 | CGRect window_frame(struct window *window)
239 | {
240 | CGRect frame = {};
241 | SLSGetWindowBounds(g_connection, window->id, &frame);
242 | return frame;
243 | }
244 |
245 | bool window_can_move(struct window *window)
246 | {
247 | Boolean result;
248 | if (AXUIElementIsAttributeSettable(window->ref, kAXPositionAttribute, &result) != kAXErrorSuccess) {
249 | result = 0;
250 | }
251 | return result;
252 | }
253 |
254 | bool window_can_resize(struct window *window)
255 | {
256 | Boolean result;
257 | if (AXUIElementIsAttributeSettable(window->ref, kAXSizeAttribute, &result) != kAXErrorSuccess) {
258 | result = 0;
259 | }
260 | return result;
261 | }
262 |
263 | bool window_is_undersized(struct window *window)
264 | {
265 | CGRect frame = window_frame(window);
266 | if (frame.size.width < 200.0f) return true;
267 | if (frame.size.height < 200.0f) return true;
268 | return false;
269 | }
270 |
271 | bool window_is_minimized(struct window *window)
272 | {
273 | Boolean result = 0;
274 | CFTypeRef value;
275 |
276 | if (AXUIElementCopyAttributeValue(window->ref, kAXMinimizedAttribute, &value) == kAXErrorSuccess) {
277 | result = CFBooleanGetValue(value);
278 | CFRelease(value);
279 | }
280 |
281 | return result || window->is_minimized;
282 | }
283 |
284 | bool window_is_fullscreen(struct window *window)
285 | {
286 | Boolean result = 0;
287 | CFTypeRef value;
288 | if (AXUIElementCopyAttributeValue(window->ref, kAXFullscreenAttribute, &value) == kAXErrorSuccess) {
289 | result = CFBooleanGetValue(value);
290 | CFRelease(value);
291 | }
292 | return result;
293 | }
294 |
295 | bool window_is_sticky(struct window *window)
296 | {
297 | bool result = false;
298 | CFArrayRef window_list_ref = cfarray_of_cfnumbers(&window->id, sizeof(uint32_t), 1, kCFNumberSInt32Type);
299 | CFArrayRef space_list_ref = SLSCopySpacesForWindows(g_connection, 0x7, window_list_ref);
300 | if (!space_list_ref) goto err;
301 |
302 | result = CFArrayGetCount(space_list_ref) > 1;
303 |
304 | CFRelease(space_list_ref);
305 | err:
306 | CFRelease(window_list_ref);
307 | return result;
308 | }
309 |
310 | bool window_is_topmost(struct window *window)
311 | {
312 | bool is_topmost = window_level(window) == CGWindowLevelForKey(LAYER_ABOVE);
313 | return is_topmost;
314 | }
315 |
316 | int window_level(struct window *window)
317 | {
318 | int level = 0;
319 | SLSGetWindowLevel(g_connection, window->id, &level);
320 | return level;
321 | }
322 |
323 | CFStringRef window_role(struct window *window)
324 | {
325 | const void *role = NULL;
326 | AXUIElementCopyAttributeValue(window->ref, kAXRoleAttribute, &role);
327 | return role;
328 | }
329 |
330 | CFStringRef window_subrole(struct window *window)
331 | {
332 | const void *srole = NULL;
333 | AXUIElementCopyAttributeValue(window->ref, kAXSubroleAttribute, &srole);
334 | return srole;
335 | }
336 |
337 | bool window_level_is_standard(struct window *window)
338 | {
339 | if (!g_normal_window_level) g_normal_window_level = CGWindowLevelForKey(4);
340 | if (!g_floating_window_level) g_floating_window_level = CGWindowLevelForKey(5);
341 |
342 | int level = window_level(window);
343 | return level == g_normal_window_level || level == g_floating_window_level;
344 | }
345 |
346 | bool window_is_standard(struct window *window)
347 | {
348 | bool standard_win = false;
349 | CFStringRef role = NULL;
350 | CFStringRef srole = NULL;
351 |
352 | if (!(role = window_role(window))) goto out;
353 | if (!(srole = window_subrole(window))) goto role;
354 |
355 | standard_win = CFEqual(role, kAXWindowRole) &&
356 | CFEqual(srole, kAXStandardWindowSubrole);
357 |
358 | CFRelease(srole);
359 | role:
360 | CFRelease(role);
361 | out:
362 | return standard_win;
363 | }
364 |
365 | bool window_is_dialog(struct window *window)
366 | {
367 | bool standard_win = false;
368 | CFStringRef role = NULL;
369 | CFStringRef srole = NULL;
370 |
371 | if (!(role = window_role(window))) goto out;
372 | if (!(srole = window_subrole(window))) goto role;
373 |
374 | standard_win = CFEqual(role, kAXWindowRole) &&
375 | CFEqual(srole, kAXDialogSubrole);
376 |
377 | CFRelease(srole);
378 | role:
379 | CFRelease(role);
380 | out:
381 | return standard_win;
382 | }
383 |
384 | bool window_is_popover(struct window *window)
385 | {
386 | CFStringRef role = window_role(window);
387 | if (!role) return false;
388 |
389 | bool result = CFEqual(role, kAXPopoverRole);
390 | CFRelease(role);
391 |
392 | return result;
393 | }
394 |
395 | bool window_is_unknown(struct window *window)
396 | {
397 | CFStringRef subrole = window_subrole(window);
398 | if (!subrole) return false;
399 |
400 | bool result = CFEqual(subrole, kAXUnknownSubrole);
401 | CFRelease(subrole);
402 |
403 | return result;
404 | }
405 |
406 | struct window *window_create(struct application *application, AXUIElementRef window_ref, uint32_t window_id)
407 | {
408 | struct window *window = malloc(sizeof(struct window));
409 | memset(window, 0, sizeof(struct window));
410 |
411 | window->application = application;
412 | window->ref = window_ref;
413 | window->id = window_id;
414 | SLSGetWindowOwner(g_connection, window->id, &window->connection);
415 | window->is_minimized = window_is_minimized(window);
416 | window->is_fullscreen = window_is_fullscreen(window) || space_is_fullscreen(window_space(window));
417 | window->id_ptr = malloc(sizeof(uint32_t *));
418 | *window->id_ptr = &window->id;
419 | window->has_shadow = true;
420 |
421 | if ((window_is_standard(window)) || (window_is_dialog(window))) {
422 | border_window_create(window);
423 |
424 | if ((!window->application->is_hidden) &&
425 | (!window->is_minimized) &&
426 | (!window->is_fullscreen)) {
427 | border_window_refresh(window);
428 | }
429 | }
430 |
431 | return window;
432 | }
433 |
434 | void window_destroy(struct window *window)
435 | {
436 | border_window_destroy(window);
437 | CFRelease(window->ref);
438 | free(window->id_ptr);
439 | free(window);
440 | }
441 |
--------------------------------------------------------------------------------
/src/window.h:
--------------------------------------------------------------------------------
1 | #ifndef WINDOW_H
2 | #define WINDOW_H
3 |
4 | extern int SLSMainConnectionID(void);
5 | extern CGError SLSGetWindowBounds(int cid, uint32_t wid, CGRect *frame);
6 | extern CGError SLSGetWindowLevel(int cid, uint32_t wid, int *level);
7 | extern CGError SLSCopyWindowProperty(int cid, uint32_t wid, CFStringRef property, CFTypeRef *value);
8 | extern CFStringRef SLSCopyManagedDisplayForWindow(int cid, uint32_t wid);
9 | extern CFStringRef SLSCopyBestManagedDisplayForRect(int cid, CGRect rect);
10 | extern CFArrayRef SLSCopySpacesForWindows(int cid, int selector, CFArrayRef window_list);
11 |
12 | const CFStringRef kAXFullscreenAttribute = CFSTR("AXFullScreen");
13 |
14 | #define AX_WINDOW_MINIMIZED_INDEX 0
15 | #define AX_WINDOW_DEMINIMIZED_INDEX 1
16 | #define AX_WINDOW_DESTROYED_INDEX 2
17 |
18 | #define AX_WINDOW_DESTROYED (1 << AX_WINDOW_DESTROYED_INDEX)
19 | #define AX_WINDOW_MINIMIZED (1 << AX_WINDOW_MINIMIZED_INDEX)
20 | #define AX_WINDOW_DEMINIMIZED (1 << AX_WINDOW_DEMINIMIZED_INDEX)
21 | #define AX_WINDOW_ALL (AX_WINDOW_DESTROYED |\
22 | AX_WINDOW_MINIMIZED |\
23 | AX_WINDOW_DEMINIMIZED)
24 |
25 | static CFStringRef ax_window_notification[] =
26 | {
27 | [AX_WINDOW_DESTROYED_INDEX] = kAXUIElementDestroyedNotification,
28 | [AX_WINDOW_MINIMIZED_INDEX] = kAXWindowMiniaturizedNotification,
29 | [AX_WINDOW_DEMINIMIZED_INDEX] = kAXWindowDeminiaturizedNotification
30 | };
31 |
32 | struct window
33 | {
34 | struct application *application;
35 | AXUIElementRef ref;
36 | int connection;
37 | uint32_t id;
38 | uint32_t **volatile id_ptr;
39 | uint8_t notification;
40 | struct border border;
41 | bool has_shadow;
42 | bool is_fullscreen;
43 | bool is_minimized;
44 | bool is_floating;
45 | float rule_alpha;
46 | bool rule_manage;
47 | bool rule_fullscreen;
48 | };
49 |
50 | CFStringRef window_display_uuid(struct window *window);
51 | int window_display_id(struct window *window);
52 | uint64_t window_space(struct window *window);
53 | uint64_t *window_space_list(struct window *window, int *count);
54 | void window_serialize(FILE *rsp, struct window *window);
55 | char *window_title(struct window *window);
56 | CGRect window_ax_frame(struct window *window);
57 | CGRect window_frame(struct window *window);
58 | int window_level(struct window *window);
59 | CFStringRef window_role(struct window *window);
60 | CFStringRef window_subrole(struct window *window);
61 | bool window_can_move(struct window *window);
62 | bool window_can_resize(struct window *window);
63 | bool window_level_is_standard(struct window *window);
64 | bool window_is_undersized(struct window *window);
65 | bool window_is_minimized(struct window *window);
66 | bool window_is_fullscreen(struct window *window);
67 | bool window_is_sticky(struct window *window);
68 | bool window_is_topmost(struct window *window);
69 | bool window_is_standard(struct window *window);
70 | bool window_is_dialog(struct window *window);
71 | bool window_is_popover(struct window *window);
72 | bool window_is_unknown(struct window *window);
73 | bool window_observe(struct window *window);
74 | void window_unobserve(struct window *window);
75 | struct window *window_create(struct application *application, AXUIElementRef window_ref, uint32_t window_id);
76 | void window_destroy(struct window *window);
77 |
78 | #endif
79 |
--------------------------------------------------------------------------------
/src/workspace.h:
--------------------------------------------------------------------------------
1 | #ifndef WORKSPACE_H
2 | #define WORKSPACE_H
3 |
4 | @interface workspace_context : NSObject {
5 | }
6 | - (id)init;
7 | @end
8 |
9 | void workspace_event_handler_init(void **context);
10 | void workspace_event_handler_begin(void **context);
11 | void workspace_event_handler_end(void *context);
12 |
13 | #endif
14 |
--------------------------------------------------------------------------------
/src/workspace.m:
--------------------------------------------------------------------------------
1 | #include "workspace.h"
2 |
3 | extern struct event_loop g_event_loop;
4 |
5 | void workspace_event_handler_init(void **context)
6 | {
7 | workspace_context *ws_context = [workspace_context alloc];
8 | *context = ws_context;
9 | }
10 |
11 | void workspace_event_handler_begin(void **context)
12 | {
13 | workspace_context *ws_context = *context;
14 | [ws_context init];
15 | }
16 |
17 | void workspace_event_handler_end(void *context)
18 | {
19 | workspace_context *ws_context = (workspace_context *) context;
20 | [ws_context dealloc];
21 | }
22 |
23 | @implementation workspace_context
24 | - (id)init
25 | {
26 | if ((self = [super init])) {
27 | [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
28 | selector:@selector(activeDisplayDidChange:)
29 | name:@"NSWorkspaceActiveDisplayDidChangeNotification"
30 | object:nil];
31 |
32 | [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
33 | selector:@selector(activeSpaceDidChange:)
34 | name:NSWorkspaceActiveSpaceDidChangeNotification
35 | object:nil];
36 |
37 | [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
38 | selector:@selector(didWake:)
39 | name:NSWorkspaceDidWakeNotification
40 | object:nil];
41 |
42 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self
43 | selector:@selector(didChangeMenuBarHiding:)
44 | name:@"AppleInterfaceMenuBarHidingChangedNotification"
45 | object:nil];
46 | }
47 |
48 | return self;
49 | }
50 |
51 | - (void)dealloc
52 | {
53 | [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
54 | [[NSNotificationCenter defaultCenter] removeObserver:self];
55 | [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
56 | [super dealloc];
57 | }
58 |
59 | - (void)didWake:(NSNotification *)notification
60 | {
61 | struct event *event = event_create(&g_event_loop, SYSTEM_WOKE, NULL);
62 | event_loop_post(&g_event_loop, event);
63 | }
64 |
65 | - (void)didChangeMenuBarHiding:(NSNotification *)notification
66 | {
67 | struct event *event = event_create(&g_event_loop, MENU_BAR_HIDDEN_CHANGED, NULL);
68 | event_loop_post(&g_event_loop, event);
69 | }
70 |
71 | - (void)activeDisplayDidChange:(NSNotification *)notification
72 | {
73 | struct event *event = event_create(&g_event_loop, DISPLAY_CHANGED, NULL);
74 | event_loop_post(&g_event_loop, event);
75 | }
76 |
77 | - (void)activeSpaceDidChange:(NSNotification *)notification
78 | {
79 | struct event *event = event_create(&g_event_loop, SPACE_CHANGED, NULL);
80 | event_loop_post(&g_event_loop, event);
81 | }
82 |
83 | @end
84 |
--------------------------------------------------------------------------------